Skip to content

feat(meta): language-metadata chip under each language tag (#83) - #124

Merged
mkpoli merged 16 commits into
masterfrom
feat/lang-metadata-chip
Jun 14, 2026
Merged

mkpoli merged 16 commits into
masterfrom
feat/lang-metadata-chip

Conversation

@mkpoli

@mkpoli mkpoli commented May 26, 2026 •

Copy link
Copy Markdown
Owner

Summary

Closes #83. Adds a small grey chip beneath each sentence's language tag showing family · typology · morphology — e.g. "Japonic · SOV · agglutinative" under JA, "Romance · SVO · fusional" under FR. A title attribute on hover extends the line with the full family chain and the script name.

This is the standout feature LangMap has and no peer authoring tool does: turns the diagram into an educational artefact, not just an alignment graphic.

Data layer

  • `src/lib/lang-meta.ts` — 70+ hand-curated BCP-47 entries covering every locale we ship UI translations for plus the major likely-user picks. Cross-referenced against WALS, Glottolog, Ethnologue.
  • `getLangMeta()` does graceful subtag fallback (`en-US` → `en`, `de-CH` → `de`) but preserves curated variant entries (`ja-Hira` ≠ `ja`, `ko-Kore` ≠ `ko`).
  • Includes 7 unit tests under `bun test`: exact-match, region fallback, variant preservation, every-field-present invariant, and a UI-locales coverage gate.

UI layer

  • New `showLangMeta` boolean param flowing Output / Parameters / +page.
  • Off by default — opt-in via a checkbox in the Text panel labelled "Show family / typology".
  • Persisted alongside the other parameter knobs in the unified localStorage snapshot.
  • The chip uses muted styling (`var(--color-text-faint)`, ~62% font size, no font-weight) so it never out-competes the language label.

i18n

  • Two new keys (`params_show_lang_meta`, `params_show_lang_meta_on`) translated into all 31 locales — 24 major + the 6 rare/historical/constructed I covered earlier + Ainu. No English fallbacks anywhere.

Test plan

  • `bun run check` — 0 errors
  • `bun run lint` — clean
  • `bun run test` — Playwright 7/7 green
  • `bun run test:unit` — 35 unit tests pass (29 existing + 7 new lang-meta)
  • Manually: toggle on in Text panel → chips appear under each language tag
  • Manually: hover a chip → tooltip shows full family + script
  • Manually: change locale → chip text follows (chip itself stays English; family / typology / morphology are technical labels not yet localised)

Summary by CodeRabbit

  • New Features
    • Added a “Language info / family typology” option to display per-language metadata chips beneath each language tag.
    • Enabled inline editing of the metadata chip; changes are saved on commit, and can be restored with Escape.
    • Added support for metadata overrides with persisted toggle state and new UI text translations across 30+ locales.
  • Tests
    • Added automated coverage for language-metadata lookup behavior, including exact matches, fallback stripping, and required metadata fields.

Closes #83.

Adds a small grey chip beneath each sentence's language tag showing
family · typology · morphology — e.g. "Japonic · SOV · agglutinative"
under JA, "Romance · SVO · fusional" under FR. A tooltip on hover
extends the line with the full family chain and the script name.

This is the standout feature LangMap has and no peer authoring tool
does: turns the diagram into an educational artefact, not just an
alignment graphic.

Data:
- src/lib/lang-meta.ts — 70+ hand-curated BCP-47 entries covering
  every locale we ship UI translations for plus the major likely-user
  picks (Indo-European branches, Sino-Tibetan, Japonic, Koreanic,
  Ainu, Turkic, Mongolic, Uralic, Afro-Asiatic, Dravidian,
  Austroasiatic, Austronesian, Tai-Kadai, Niger-Congo, Kartvelian,
  isolates, constructed langs). Cross-referenced against WALS,
  Glottolog, Ethnologue.
- getLangMeta() falls back from full BCP-47 (en-US → en, de-CH → de)
  while preserving curated variant entries (ja-Hira, ko-Kore stay
  distinct from ja, ko).

UI:
- showLangMeta param wired through Output / Parameters / +page.
- Off by default — opt-in via a checkbox in the Text panel ("Show
  family / typology"). Persisted to localStorage with the other params.
- 7 unit tests cover exact-match, region-subtag fallback, variant
  preservation, and uiLocales coverage.

i18n:
- Two new keys: params_show_lang_meta, params_show_lang_meta_on.
- Translated into all 31 locales (24 major + ain/grc/la/ko-Kore/
  ja-Hira/tok/ia) inline. No English fallbacks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented May 26, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
word-order Ready Ready Preview, Comment Jun 14, 2026 1:46pm

Request Review

@coderabbitai

coderabbitai Bot commented May 26, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 448946a9-a95a-4ad8-a194-2dc5501d5a83

📥 Commits

Reviewing files that changed from the base of the PR and between 81c1217 and c442a2e.

📒 Files selected for processing (3)
  • src/lib/Parameters.svelte
  • src/lib/SentenceInput.svelte
  • src/routes/+page.svelte
✅ Files skipped from review due to trivial changes (1)
  • src/lib/SentenceInput.svelte

📝 Walkthrough

Walkthrough

This PR implements the language metadata display feature from issue #83: inline linguistic metadata chips under language tags, editable per-sentence overrides, a toggleable showLangMeta setting, persistence, and translations across 32 locales.

Changes

Language Metadata Display Feature

Layer / File(s) Summary
Language metadata data structure and lookup
src/lib/lang-meta.ts, src/lib/lang-meta.test.ts
LangMeta types and curated lookup table by BCP-47 code; getLangMeta() resolves codes via exact match or subtag-stripping fallback; tests validate lookup behavior, data consistency, and coverage across locale codes.
Type definitions and state persistence
src/lib/types.ts, src/lib/persisted-params.ts
Sentence gains optional displayMeta for per-sentence overrides; normalizeSentence preserves displayMeta; ParamsSnapshot adds showLangMeta.
Internationalization setup
src/i18n/i18n-svelte.ts, project.inlang/messages/*
Adds params_show_lang_meta and params_show_lang_meta_on to locale store and translates across 32 message files.
Output component metadata rendering
src/lib/Output.svelte
Adds showLangMeta prop; computes metadata via getLangMeta; renders inline-editable .tag-meta chip with blur/Enter/Escape semantics; dispatches renameMeta; adds ResizeObserver and editableText action; CSS for column layout and absolute positioning.
Parameters component settings UI
src/lib/Parameters.svelte
Adds showLangMeta bindable prop with checkbox control; includes localized label and enabled-state text from translations; adds .meta-toggle styling for consistency.
Page-level state management
src/routes/+page.svelte
Adds reactive showLangMeta with persistence; restores on mount; passes to Output and binds to Parameters; handles renameMeta events to update/delete sentence displayMeta.

Sequence Diagram(s)

sequenceDiagram
  participant Page
  participant Output
  participant Parameters
  participant getLangMeta

  Page->>Output: showLangMeta, sentences
  Output->>getLangMeta: getLangMeta(lang)
  getLangMeta-->>Output: LangMeta or null
  Output->>Output: Render tag-meta chip
  Output-->>Page: on:renameMeta({sentence, displayMeta})
  
  Page->>Parameters: bind:showLangMeta
  Parameters->>Parameters: Render checkbox with label
  Parameters-->>Page: Toggle showLangMeta
  Page->>Page: Save to persisted params
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🐰 I tucked small chips beneath each tongue,
so learners hum the families unsung.
Typology, script, a tiny lore—
edit a line and learn some more! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(meta): language-metadata chip under each language tag' accurately describes the primary change—adding linguistic metadata display beneath language tags.
Linked Issues check ✅ Passed The PR fully implements issue #83 objectives: hand-curated language metadata (family/typology/morphology/script) lookups, graceful subtag fallback, toggleable chip display, comprehensive i18n (31 locales), persistent state management, and proper test coverage.
Out of Scope Changes check ✅ Passed All changes are within scope—strictly implementing issue #83 requirements. File changes include expected message translations, metadata infrastructure (lang-meta.ts), UI components (Output/Parameters), state management (persisted-params), type updates (types.ts), and comprehensive tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/lang-metadata-chip

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

The metadata chip ("Germanic · SVO · fusional") under each language tag
is auto-generated from lang-meta.ts. Make it inline-editable so the user
can override the line per sentence — same pattern as the language label
will use once #126 lands.

- New optional Sentence.displayMeta field; empty string is preserved as
  override, undefined means use the auto-generated default.
- Chip is now contenteditable="plaintext-only". Enter blurs / commits,
  Esc cancels, blur dispatches renameMeta with undefined when the field
  is cleared. Customised state gets italic + accent-coloured text and a
  faint focus background.
- normalizeSentence now round-trips displayName and displayMeta through
  saveDoc/loadDoc — previously both were silently stripped on reload.
- params_show_lang_meta: 'Itak orowapaye' → 'Itak uepeker'
- params_show_lang_meta_on: 'Aynu utari kor itak ari' → 'Ikiri sinrit'

Confirmed wording from mkpoli.
Same UX rule as #126 / [[feedback-ux-inline-over-dialog]]: the final-
render diagram must look identical whether a user has overridden the
metadata chip or not. The italic + accent that I'd added to mirror the
tag-text override pattern leaked the customised state into the exported
PNG / SVG / PDF, which is exactly what the user asked us to avoid.

Drop the .tag-meta.customised CSS and the class binding on the span.
The contenteditable affordance + focus background remain, so the user
can still edit the chip inline; we just no longer style the resting
state any differently when an override is set.
UX request: "We should have language info edited and styled when edited
in Input fieldset". The chip in the Output diagram is now display-only;
the editing affordance + italic/accent customised state lives in the
SentenceInput row instead, so the exported artifact never carries any
visual indication that the metadata was customised.

- Output.svelte: drop contenteditable / onblur / onkeydown / focus
  styling on .tag-meta. Also drop the unused renameMeta dispatcher
  signature — the event is now emitted from SentenceInput.
- SentenceInput.svelte: when in modify mode and the Parameters chip is
  enabled (showLangMeta prop), render a single-row editor labelled with
  $LL.params.showLangMeta. Placeholder shows the auto-generated default
  (family · typology · morphology) so the user sees what they're
  overriding; empty value clears the override. Italic + accent on both
  label and input mark the customised state; the diagram stays plain.
  Enter blurs/commits, Esc reverts to the stored override.
- +page.svelte: pass {showLangMeta} into SentenceInput and move the
  renameMeta event handler from <Output> to <SentenceInput>.

Grid layout grew a new 'm m m' row in both the desktop and mobile
input-form templates so the editor sits between the lang/buttons row
and the guidance row.
Per UX feedback: "instead of putting option to Text, group them
together in Input fieldset". The toggle and the per-sentence editor
now live together in the same row of SentenceInput so the user gets
all language-related controls in one place instead of jumping between
the Input fieldset and the Parameters → Text panel.

- Parameters.svelte: drop the `showLangMeta` prop + the "Show family
  / typology" checkbox.
- SentenceInput.svelte: `showLangMeta` is now `$bindable` and the
  toggle UI sits at the top of the new meta-row, with the per-sentence
  override input appearing below it on its own grid row when (a) the
  toggle is on, (b) modify mode is active, and (c) the current
  language has metadata coverage.
- +page.svelte: bind:showLangMeta on SentenceInput, dropped from
  Parameters. Existing localStorage persistence + the prop into
  <Output> are unchanged — the master state still lives on +page.
…y plain

User: "i want inline editing but not edited style it turns blue and
italic". So click-to-edit on the chip is back, but the visual indicator
of "this is customised" stays only in SentenceInput's meta-row.

- Re-add contenteditable="plaintext-only" + onblur/onkeydown to the
  .tag-meta span in Output, plus the renameMeta dispatcher signature.
  Enter commits, Esc reverts to the stored value.
- Restore the chip's edit affordance CSS (cursor: text, outline: none,
  focus background) — but no italic/accent for the customised state;
  that's intentionally absent so the rendered/exported diagram looks
  identical for default vs override.
- +page.svelte now listens to renameMeta on BOTH <Output> (inline) and
  <SentenceInput> (the meta-row editor) — two entry points, same
  reducer that updates sentence.displayMeta.
User: "The line rendering is broken due to added height due to language
info". The chip was a flex column child of .tag, which made the .tag
grid item taller by the chip's height. The output's grid rows size to
the tallest cell, so every row grew and the lines drawLines emits had
to span the extra chip-height in addition to the user-configured
verticalGap — visibly stretching the connector geometry.

Pull the chip out of flow: position: absolute, anchored top: 100%,
right: 0 against a now-relative .tag. The chip sits in the column gap
below the tag-row on the tag's x-axis (connectors live in the
sentence-body column, so they don't visually collide), and the grid
row height is back to just the tag-row's natural height.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/types.ts (1)

141-149: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

displayMeta/displayName are dropped for { lang, words, ... } sentence objects.

The non-token normalization path does not carry these optional overrides, so custom metadata/label can be silently lost.

Suggested patch
 	const sentenceObject = sentence as {
 		lang: string;
 		words?: string[];
 		glosses?: string[];
 		showGloss?: boolean;
+		displayName?: string;
+		displayMeta?: string;
 	};
 
-	return createSentence(sentenceObject.lang, sentenceObject.words ?? [], sentenceObject.glosses ?? [], sentenceObject.showGloss ?? false);
+	const normalized = createSentence(
+		sentenceObject.lang,
+		sentenceObject.words ?? [],
+		sentenceObject.glosses ?? [],
+		sentenceObject.showGloss ?? false
+	);
+	return {
+		...normalized,
+		...(sentenceObject.displayName !== undefined ? { displayName: sentenceObject.displayName } : {}),
+		...(sentenceObject.displayMeta !== undefined ? { displayMeta: sentenceObject.displayMeta } : {})
+	};
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/types.ts` around lines 141 - 149, The normalization path is dropping
optional displayMeta/displayName when coercing to the { lang, words, ... }
shape; update the cast and call site so you preserve these fields (e.g., read
displayMeta and displayName from sentence into sentenceObject and pass them into
createSentence or apply them to the returned Sentence), updating the
createSentence invocation and/or its signature to accept/display those optional
overrides (identify the change around the sentenceObject variable and the
createSentence(...) call).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/lang-meta.ts`:
- Around line 92-94: The LANG_META lookup is missing canonical BCP-47 script
keys 'zh-Hans' and 'zh-Hant', so lookups like
getLangMeta('zh-Hans')/getLangMeta('zh-Hant') fall back to generic 'zh'; add
entries 'zh-Hans' (copying the object for 'zh-HanS') and 'zh-Hant' (copying the
object for 'zh-HanT') to the LANG_META map so exact lookups succeed, keeping the
same fields (family, typology, morphology, script, speakers) as the existing
HanS/HanT entries referenced in LANG_META and used by getLangMeta.

In `@src/lib/Output.svelte`:
- Around line 727-729: The tooltip (title) is being removed when metaCustomised
is true; keep the title always set so hover shows full info even for edited
chips: in the component using metaCustomised, remove the conditional that sets
title to an empty string and always set title to `${meta.family.join(' · ')} ·
${meta.typology} · ${meta.morphology} · ${meta.script}` (leave
lang={metaCustomised ? undefined : 'en'} and contenteditable as-is); update the
JSX/markup where title is assigned (referencing metaCustomised and title) so the
title value is constant regardless of metaCustomised.

In `@src/lib/SentenceInput.svelte`:
- Around line 323-324: The master global toggle input (id="show-lang-meta",
bind:checked={showLangMeta}) is incorrectly disabled when defaultMetaText is
falsy; remove the disabled={!defaultMetaText} from that input so the global
setting can always be toggled, and keep the availability logic for per-sentence
override in the block that uses {`#if` modifying !== -1 && showLangMeta &&
defaultMetaText} so the per-row control still respects defaultMetaText and
showLangMeta.

---

Outside diff comments:
In `@src/lib/types.ts`:
- Around line 141-149: The normalization path is dropping optional
displayMeta/displayName when coercing to the { lang, words, ... } shape; update
the cast and call site so you preserve these fields (e.g., read displayMeta and
displayName from sentence into sentenceObject and pass them into createSentence
or apply them to the returned Sentence), updating the createSentence invocation
and/or its signature to accept/display those optional overrides (identify the
change around the sentenceObject variable and the createSentence(...) call).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a15ea0ee-86dc-42f2-af8e-41bfd46ab9c0

📥 Commits

Reviewing files that changed from the base of the PR and between 63950f0 and a1a2e53.

📒 Files selected for processing (40)
  • project.inlang/messages/ain.json
  • project.inlang/messages/ar.json
  • project.inlang/messages/bg.json
  • project.inlang/messages/bn.json
  • project.inlang/messages/de.json
  • project.inlang/messages/en.json
  • project.inlang/messages/eo.json
  • project.inlang/messages/es.json
  • project.inlang/messages/fa.json
  • project.inlang/messages/fi.json
  • project.inlang/messages/fr.json
  • project.inlang/messages/grc.json
  • project.inlang/messages/hi.json
  • project.inlang/messages/ia.json
  • project.inlang/messages/id.json
  • project.inlang/messages/it.json
  • project.inlang/messages/ja-Hira.json
  • project.inlang/messages/ja.json
  • project.inlang/messages/ko-Kore.json
  • project.inlang/messages/ko.json
  • project.inlang/messages/la.json
  • project.inlang/messages/nl.json
  • project.inlang/messages/pl.json
  • project.inlang/messages/pt.json
  • project.inlang/messages/ru.json
  • project.inlang/messages/th.json
  • project.inlang/messages/tok.json
  • project.inlang/messages/tr.json
  • project.inlang/messages/uk.json
  • project.inlang/messages/vi.json
  • project.inlang/messages/zh-HanS.json
  • project.inlang/messages/zh-HanT.json
  • src/i18n/i18n-svelte.ts
  • src/lib/Output.svelte
  • src/lib/SentenceInput.svelte
  • src/lib/lang-meta.test.ts
  • src/lib/lang-meta.ts
  • src/lib/persisted-params.ts
  • src/lib/types.ts
  • src/routes/+page.svelte

Comment thread src/lib/lang-meta.ts
Comment thread src/lib/Output.svelte Outdated
Comment thread src/lib/SentenceInput.svelte Outdated
User: "vertically still wrong, it's not refreshed". The chip-out-of-
flow CSS fix correctly stopped the row from growing, but drawLines
wasn't re-running when showLangMeta toggled or when a displayMeta
override changed the chip's content width — so the connectors stayed
pinned to the pre-chip word positions and looked vertically off.

Two fixes:

1. Add `void showLangMeta` and `void sentences` to the existing
   drawLines run() block, wrapped in tick() so the chip's DOM mount /
   unmount completes before we measure.
2. Add a ResizeObserver on the <output> element. Any layout change
   (chip toggle, font load, browser zoom, viewport reflow) triggers a
   fresh drawLines call. drawLines is read-only on layout, so no
   feedback loop.
User: "I still don't like it like it's too long". Drop the family
component from the default chip text; "SVO · fusional" carries the
typologically useful info and is roughly half the width of the
previous "Germanic · SVO · fusional". The full family chain stays
available on hover via the title attribute.

Applied in both Output (chip rendering) and SentenceInput
(placeholder for the meta-row editor) so the default text the user
sees is consistent across surfaces.
User: "We should allow unknown language input their info". Previously
the chip toggle was disabled and the editor hidden for any BCP-47 tag
that lang-meta.ts didn't recognise — meaning the user could only show
info for the ~70 hand-curated entries.

Now:

- Output.svelte: the chip renders when `showLangMeta && currentMetaText`
  (instead of `showLangMeta && meta`). For unknown languages, that
  means the user's displayMeta override is enough to bring the chip
  into existence — no curated entry needed.
- SentenceInput.svelte: drop the `disabled={!defaultMetaText}` gate on
  the toggle and the `&& defaultMetaText` gate on the editor. The
  placeholder is whatever the auto-generated default is (empty for
  unknown languages — the input is just blank, ready for typing).

When lang-meta has no entry, the chip's lang/title fallback strips the
`lang="en"` hint and the auto-family-chain title since neither applies.
The chip was gated behind `showLangMeta && currentMetaText`, so a language
with no lang-meta coverage and no override rendered no chip at all — there
was nothing to click to add custom info directly in the diagram, the same
"impossible to edit the empty one" bug fixed for the language tag in #126.

Render the chip whenever the feature is on, and give the empty state a
clickable box: min-width for a horizontal target and a zero-width no-break
space (::before) for line height, plus a dashed hover outline for
discoverability. None of it is part of innerText, so save/revert is
unaffected and the empty chip stays invisible on export.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…chip

# Conflicts:
#	project.inlang/messages/ar.json
#	project.inlang/messages/bg.json
#	project.inlang/messages/bn.json
#	project.inlang/messages/de.json
#	project.inlang/messages/eo.json
#	project.inlang/messages/es.json
#	project.inlang/messages/fa.json
#	project.inlang/messages/fi.json
#	project.inlang/messages/fr.json
#	project.inlang/messages/grc.json
#	project.inlang/messages/hi.json
#	project.inlang/messages/ia.json
#	project.inlang/messages/id.json
#	project.inlang/messages/it.json
#	project.inlang/messages/ja-Hira.json
#	project.inlang/messages/ja.json
#	project.inlang/messages/ko-Kore.json
#	project.inlang/messages/ko.json
#	project.inlang/messages/la.json
#	project.inlang/messages/nl.json
#	project.inlang/messages/pl.json
#	project.inlang/messages/pt.json
#	project.inlang/messages/ru.json
#	project.inlang/messages/th.json
#	project.inlang/messages/tok.json
#	project.inlang/messages/tr.json
#	project.inlang/messages/uk.json
#	project.inlang/messages/vi.json
#	project.inlang/messages/zh-HanS.json
#	project.inlang/messages/zh-HanT.json
#	src/lib/Output.svelte
The empty chip rendered a clickable box but couldn't really be edited:

- Typing into it doubled the text ("HELLO" → "HELLOHELLO"). Cause: the chip's
  content came from a reactive `{currentMetaText}` child. When the chip starts
  empty, typing creates a second browser text node; the next reactive update
  then writes Svelte's own node too, leaving two. Own the content imperatively
  via a `use:editableText` action that keeps a single node and only re-syncs
  from state when the field isn't focused and the value differs.

- Clearing the text left a stray <br>, which kept the chip out of :empty and
  collapsed its clickable box to zero width, so an emptied chip could never be
  edited again. Normalize textContent on blur to restore a clean :empty node.

Also guards Enter/Escape against IME composition so CJK input into the chip
isn't cut off mid-candidate. Verified end-to-end (type / clear / re-edit) with
an uncovered language; bun run check is clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The chip was right-aligned (right: 0) to the tag's right edge, while the
tagAlignment merge left the labels center-aligned. Because each tag's width
varied with its label, the chips landed at different x-offsets — a jagged
column shifted right of the labels.

Center the chip on the tag's x-axis instead, and take the (invisible) rename
button out of flow so the tag's width tracks the label alone — otherwise the
button's width pushed the tag center ~half-a-button right of the label. The
chip now lines up exactly under the label for left/center/right tagAlignment.
Verified in-browser: chip center == label center in all three modes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/lib/Output.svelte (1)

331-338: ⚡ Quick win

Use Set-based deduplication to avoid quadratic endpoint processing.

Line 333–338 does linear scans per endpoint (out.some(...)), which scales poorly during frequent redraws.

♻️ Proposed refactor
 function uniqueEndpoints(lines: Line[]): Array<{ x: number; y: number; color: string }> {
-	const out: Array<{ x: number; y: number; color: string }> = [];
-	const has = (x: number, y: number, color: string) =>
-		out.some((p) => Math.round(p.x) === Math.round(x) && Math.round(p.y) === Math.round(y) && p.color === color);
+	const out: Array<{ x: number; y: number; color: string }> = [];
+	const seen = new Set<string>();
+	const push = (x: number, y: number, color: string) => {
+		const key = `${Math.round(x)}:${Math.round(y)}:${color}`;
+		if (seen.has(key)) return;
+		seen.add(key);
+		out.push({ x, y, color });
+	};
 	for (const [x1, y1, x2, y2, color] of lines) {
-		if (!has(x1, y1, color)) out.push({ x: x1, y: y1, color });
-		if (!has(x2, y2, color)) out.push({ x: x2, y: y2, color });
+		push(x1, y1, color);
+		push(x2, y2, color);
 	}
 	return out;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/Output.svelte` around lines 331 - 338, The uniqueEndpoints function
uses a linear search with out.some(...) inside the loop to check for duplicate
endpoints, creating quadratic time complexity. Replace this approach by using a
Set-based deduplication strategy where you create a Set that tracks previously
added endpoints using a key that combines the rounded x and y coordinates along
with the color. Before pushing each endpoint to the out array, check if it
exists in the Set using this key instead of the has function. This will reduce
the time complexity from O(n²) to O(n) and improve performance during frequent
redraws.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/lib/Output.svelte`:
- Around line 331-338: The uniqueEndpoints function uses a linear search with
out.some(...) inside the loop to check for duplicate endpoints, creating
quadratic time complexity. Replace this approach by using a Set-based
deduplication strategy where you create a Set that tracks previously added
endpoints using a key that combines the rounded x and y coordinates along with
the color. Before pushing each endpoint to the out array, check if it exists in
the Set using this key instead of the has function. This will reduce the time
complexity from O(n²) to O(n) and improve performance during frequent redraws.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9223d82b-1aa2-44f0-a602-31fd8e2a63d8

📥 Commits

Reviewing files that changed from the base of the PR and between 2465508 and 81c1217.

📒 Files selected for processing (37)
  • project.inlang/messages/ain.json
  • project.inlang/messages/ar.json
  • project.inlang/messages/bg.json
  • project.inlang/messages/bn.json
  • project.inlang/messages/de.json
  • project.inlang/messages/en.json
  • project.inlang/messages/eo.json
  • project.inlang/messages/es.json
  • project.inlang/messages/fa.json
  • project.inlang/messages/fi.json
  • project.inlang/messages/fr.json
  • project.inlang/messages/grc.json
  • project.inlang/messages/hi.json
  • project.inlang/messages/ia.json
  • project.inlang/messages/id.json
  • project.inlang/messages/it.json
  • project.inlang/messages/ja-Hira.json
  • project.inlang/messages/ja.json
  • project.inlang/messages/ko-Kore.json
  • project.inlang/messages/ko.json
  • project.inlang/messages/la.json
  • project.inlang/messages/nl.json
  • project.inlang/messages/pl.json
  • project.inlang/messages/pt.json
  • project.inlang/messages/ru.json
  • project.inlang/messages/th.json
  • project.inlang/messages/tok.json
  • project.inlang/messages/tr.json
  • project.inlang/messages/uk.json
  • project.inlang/messages/vi.json
  • project.inlang/messages/zh-HanS.json
  • project.inlang/messages/zh-HanT.json
  • src/i18n/i18n-svelte.ts
  • src/lib/Output.svelte
  • src/lib/SentenceInput.svelte
  • src/lib/persisted-params.ts
  • src/routes/+page.svelte
✅ Files skipped from review due to trivial changes (12)
  • project.inlang/messages/ru.json
  • project.inlang/messages/ain.json
  • project.inlang/messages/th.json
  • project.inlang/messages/ar.json
  • project.inlang/messages/ia.json
  • project.inlang/messages/de.json
  • project.inlang/messages/pt.json
  • project.inlang/messages/en.json
  • project.inlang/messages/id.json
  • project.inlang/messages/uk.json
  • project.inlang/messages/it.json
  • project.inlang/messages/tok.json
🚧 Files skipped from review as they are similar to previous changes (18)
  • project.inlang/messages/tr.json
  • project.inlang/messages/zh-HanT.json
  • project.inlang/messages/ja-Hira.json
  • project.inlang/messages/ko.json
  • project.inlang/messages/es.json
  • project.inlang/messages/ja.json
  • project.inlang/messages/vi.json
  • project.inlang/messages/la.json
  • project.inlang/messages/pl.json
  • project.inlang/messages/fr.json
  • src/lib/persisted-params.ts
  • project.inlang/messages/ko-Kore.json
  • project.inlang/messages/fa.json
  • project.inlang/messages/grc.json
  • project.inlang/messages/zh-HanS.json
  • project.inlang/messages/fi.json
  • src/routes/+page.svelte
  • src/lib/SentenceInput.svelte

The "Show family / typology" toggle lived in the Input/edit fieldset; move it
to a new "Language info" panel in Parameters, alongside Options / Text /
Colors. Reuses the existing i18n keys (params_show_lang_meta as the panel
title, _on as the toggle label), so no new translations.

Per-sentence meta is now edited inline on the diagram chip (which works after
the earlier fix), so the input-side editor — which never propagated to the
chip anyway — is removed along with its now-dead state, dispatch, grid row,
and styles. This clears all lang-meta UI out of the input/edit area.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mkpoli
mkpoli merged commit 88c5743 into master Jun 14, 2026
5 checks passed

This branch was successfully deployed

1 active deployment
Preview — c442a2ee Deployed Jun 14, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: language metadata side-panel (family / typology / script)

1 participant