Rework the activity view: readable summaries and a before/after diff#2388
Rework the activity view: readable summaries and a before/after diff#2388myieye wants to merge 52 commits into
Conversation
…r diff The activity list now says what actually happened in each change — e.g. "Apfel › apple · Set Definition (en) to …", "Changed part of speech to Noun", "Removed semantic domain 5.2 Food" — instead of just naming the change type, with Simple and Detailed modes mirroring the browse view. The detail panel shows a real before/after diff of the entry (a read-only reuse of the editor layout) in place of the old current-version toggle. Each summary names its subject (entry headword, "headword › gloss" for a sense, a vocab object's name) and any item a change references only by id (the part of speech set, the domain/component removed or linked). Entry headwords carry their homograph number as a subscript when assigned. Those names are resolved in one batched pass per page, so naming adds no per-row queries. The list of change types is generated from the backend via Reinforced.Typings and a coverage test fails until every type has a summary. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two activity-summary wording tweaks, both prompted by review: - A reorder now reads "<entry> · Reordered sense <gloss>" — the parent entry is the subject and the moved item is named — instead of "<entry> › <gloss> · Reordered senses", which put one sense in the subject slot and read as if the whole list moved. Generalizes to examples and components: the parent is the subject, and the moved item is named when it has one. - A field edit on a vocabulary object now names its type, e.g. "part of speech Noun · Set Abbreviation (en) to …", matching how the created/edited/deleted vocab lines already name the type. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A commit that only builds one entry (its creation plus that entry's own senses and fields) now renders as the finished entry in a read-only EntryEditor, instead of a stack of per-change diff cards. It reads the backend-resolved affectedEntries, so there's no change-by-change assembly and no dependence on recognising every change type — an unfamiliar change type can't break it. Other commits keep their per-change cards, and the "(N changes)" header stays. Adds a Storybook "activity/previews" gallery covering every preview pane (entry/sense/example diffs, complex-form component, generic JSON, the no-preview fallback) and the collapsed entry view, so these can be checked without a live project. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Large commits no longer render a line per change. A commit is classified from cheap signals it already carries — its change-type keys, per-change root ids, and count — without parsing every change: building one entry collapses to "Created entry X", a batch of one create type collapses to "Created 100 semantic domains", and anything else lists the first changes (capped in Detailed mode) with the rest counted. So a 100-change sync commit is one or a few lines, and Simple mode never parses past the first change. Tests to keep this honest as the model grows — covering both ends and the "don't forget to handle it" cases: - every create change type has a bulk-collapse noun (new guard); - the view config covers every model field — a new field must be configured or explicitly excluded (new guard); - every backend change type still maps to a summary, and diff primitives render exactly the configured fields (existing guards); - backend: the resolver names a vocab subject and the referenced part of speech, alongside the existing homograph and reorder coverage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pre-PR review follow-ups: - export the two new generated types from the LcmCrdt barrel (index.ts) - give the multi-key change-type filter test a positive count assertion so it can't pass vacuously on an empty result - order multi-writing-system vocab labels by writing-system code, matching Entry.Headword(), so the shown alternative is deterministic - SenseLabel is never null — drop the misleading nullable return - remove the unused `activity` prop from ActivityItemChangePreview - trim two design-rationale comments to their load-bearing part - add translator context for the activity strings (Detailed, the Set-field lines, the "+N more" overflow) and broaden the shared "List mode" comment Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds an end-to-end enriched activity log: backend batch-resolves human-readable subject labels and root entry IDs per CRDT change via a new ChangesActivity log enrichment and change summary UI
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
The latest updates on your projects. Learn more about Argos notifications ↗︎
|
change-summary.ts keys are backend change-type discriminators and wire-format property names, all intentionally PascalCase, so disable naming-convention for the file (and the test's PascalCase payloads) rather than quoting ~50 keys. Reuse displayValue for span text instead of String()-ing an unknown, and drop an unused handler arg and an unused derived. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ReplaceComplexFormTypeChange had a summary handler but the backend never emits it — the change class isn't registered, so it's absent from the generated knownChangeTypes. Remove the dead handler and add a reverse-coverage test: every handler must map to a real generated change type. Paired with the existing forward check (every known type is handled), the two force the handler back the moment the backend registers the type. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
I just checked this out on a couple projects. It looks great! I haven't done a detailed code review, even to get the flow of how it works. But so far I like it. |
- Headwords use the best non-audio writing system + morph-type markers, so created entries show a real headword instead of "(Unknown)"; truly headword-less entries show a clear "(no headword)" placeholder. - Standardized wording (dropped inconsistent articles); data values render as subtle pills so they stand apart from the template text; long rows truncate instead of breaking the layout. - Fixed the activity page blowing out when a row is expanded (detail column min-width:0, content scrolls/wraps). - Homograph: a dedicated summary for the homograph-number patch, and the subscript now shows in the preview header. - Collapse a sense-creation commit (sense + its examples) to one line. - Moved the ChangeTypes generation marker out of a confusing public file into TypeGen (internal). Tests (resolver best-WS/morph/empty; frontend sense-collapse + homograph patch), storybook galleries, and i18n context updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
frontend/viewer/src/lib/dotnet-types/generated-types/LcmCrdt/IProjectActivity.ts (1)
17-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winWire
changeTypesto the generatedChangeTypeunion.This contract still exposes
changeTypesasstring[], so consumers keep losing typo/exhaustiveness checking even though this PR now generates the canonical backend-derived union inChangeTypes.ts. I’d thread that alias into the generator here rather than leaving the main activity surface untyped.🤖 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 `@frontend/viewer/src/lib/dotnet-types/generated-types/LcmCrdt/IProjectActivity.ts` around lines 17 - 18, The IProjectActivity contract still types changeTypes as string[], so it should be updated to use the generated ChangeType union from ChangeTypes.ts instead. Thread that alias into the IProjectActivity definition so the changeTypes field references the canonical backend-derived type rather than an untyped string array, preserving exhaustiveness and typo checking for consumers.
🤖 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 `@backend/FwLite/FwLiteShared/TypeGen/ChangeTypesCodeGenerator.cs`:
- Around line 35-39: The fallback in GetChangeTypeName is masking missing
serialized discriminators and can generate an incorrect frontend contract.
Update GetChangeTypeName to require a public static TypeName on each change type
and fail fast with an exception if it is absent or empty, so
ChangeTypesCodeGenerator only emits knownChangeTypes values from the explicit
discriminator and never silently substitutes changeType.Name.
In `@backend/FwLite/LcmCrdt/HistoryService.cs`:
- Around line 325-326: Update HistoryService’s Sense POS hydration so historical
snapshots use the commit they belong to instead of the latest state. In
ResolveSensePartOfSpeech and its callers in the diff flow, pass the
current/previous commit id through and resolve the PartOfSpeech via GetAtCommit
for each snapshot rather than GetLatest<PartOfSpeech>(). Make sure both the
current snapshot and previousSnapshot paths in the activity diff use the
commit-aware lookup so old diffs show the POS label that existed at that commit.
In `@frontend/viewer/src/lib/activity/change-summary.ts`:
- Around line 52-57: `ChangeFact` is still carrying English UI text in
`editObjectField.field` and `generic.text`, so move those values to
machine-readable identifiers and stop baking display strings into
`patchOpToObjectField()` and `humanizeType()`. Update `ChangeSummary.svelte` to
translate/render the localized labels itself using the fact’s symbols from
`change-summary.ts`, and make sure the `editObjectField` and `generic` cases no
longer depend on English text for vocab-field edits or fallback activity
messages.
- Around line 228-229: The create fallback for semantic domains in
change-summary.ts only uses the code, so unresolved summaries lose the
human-readable name. Update the CreateSemanticDomainChange handler to use the
existing semanticDomainLabel() helper, matching how semantic domains are labeled
elsewhere in this file, while leaving CreatePartOfSpeechChange unchanged.
In `@frontend/viewer/src/lib/components/dictionary/Headwords.svelte`:
- Around line 43-45: The homograph suffix condition in Headwords.svelte is too
strict because it hides the <sub> whenever headwords.length is zero, which also
suppresses the new placeholder rendering path. Update the conditional around the
entry.homographNumber display in the Headwords component so it shows whenever
showHomograph is enabled and a homograph number exists, even if the placeholder
branch is used; verify callers like ActivityItemChangePreview still render the
suffix for untitled entries.
In `@frontend/viewer/src/lib/history/HistoryView.svelte`:
- Line 106: The ActivityItem call in HistoryView.svelte is discarding the new
summary metadata by hardcoding changeTypes and changeInfo to empty arrays.
Update the record-to-activity mapping so ActivityItem receives the actual values
from record, preserving the enriched subject/type data added by this PR and
allowing the history drawer to render readable summaries.
---
Nitpick comments:
In
`@frontend/viewer/src/lib/dotnet-types/generated-types/LcmCrdt/IProjectActivity.ts`:
- Around line 17-18: The IProjectActivity contract still types changeTypes as
string[], so it should be updated to use the generated ChangeType union from
ChangeTypes.ts instead. Thread that alias into the IProjectActivity definition
so the changeTypes field references the canonical backend-derived type rather
than an untyped string array, preserving exhaustiveness and typo checking for
consumers.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: a51d3835-ac75-4cef-b52e-c714cf574d9b
⛔ Files ignored due to path filters (1)
frontend/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (48)
backend/FwLite/FwLiteShared/TypeGen/ChangeTypesCodeGenerator.csbackend/FwLite/FwLiteShared/TypeGen/ChangeTypesMarker.csbackend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.csbackend/FwLite/LcmCrdt.Tests/HistoryServiceActivityTests.csbackend/FwLite/LcmCrdt/ActivityChangeInfoResolver.csbackend/FwLite/LcmCrdt/HistoryService.csfrontend/viewer/package.jsonfrontend/viewer/src/lib/activity/ActivityItem.sveltefrontend/viewer/src/lib/activity/ActivityItemChangePreview.sveltefrontend/viewer/src/lib/activity/ActivityListViewOptions.sveltefrontend/viewer/src/lib/activity/ActivityView.sveltefrontend/viewer/src/lib/activity/ChangeSummary.sveltefrontend/viewer/src/lib/activity/change-summary.test.tsfrontend/viewer/src/lib/activity/change-summary.tsfrontend/viewer/src/lib/components/ListItem.sveltefrontend/viewer/src/lib/components/dictionary/Headwords.sveltefrontend/viewer/src/lib/dotnet-types/generated-types/LcmCrdt/ChangeTypes.tsfrontend/viewer/src/lib/dotnet-types/generated-types/LcmCrdt/IActivityChangeInfo.tsfrontend/viewer/src/lib/dotnet-types/generated-types/LcmCrdt/IChangeContext.tsfrontend/viewer/src/lib/dotnet-types/generated-types/LcmCrdt/IProjectActivity.tsfrontend/viewer/src/lib/dotnet-types/generated-types/LcmCrdt/index.tsfrontend/viewer/src/lib/entry-editor/diff-view/DiffEntryPrimitive.sveltefrontend/viewer/src/lib/entry-editor/diff-view/DiffExamplePrimitive.sveltefrontend/viewer/src/lib/entry-editor/diff-view/DiffMultiSelect.sveltefrontend/viewer/src/lib/entry-editor/diff-view/DiffMultiString.sveltefrontend/viewer/src/lib/entry-editor/diff-view/DiffRichText.sveltefrontend/viewer/src/lib/entry-editor/diff-view/DiffSelect.sveltefrontend/viewer/src/lib/entry-editor/diff-view/DiffSensePrimitive.sveltefrontend/viewer/src/lib/entry-editor/diff-view/DiffShell.sveltefrontend/viewer/src/lib/entry-editor/diff-view/DiffText.sveltefrontend/viewer/src/lib/entry-editor/diff-view/DiffValue.sveltefrontend/viewer/src/lib/entry-editor/diff-view/diff-field-coverage.test.tsfrontend/viewer/src/lib/entry-editor/diff-view/inline-diff.test.tsfrontend/viewer/src/lib/entry-editor/diff-view/inline-diff.tsfrontend/viewer/src/lib/history/HistoryView.sveltefrontend/viewer/src/lib/storage/project-storage.svelte.tsfrontend/viewer/src/lib/views/entity-config-coverage.test.tsfrontend/viewer/src/locales/en.pofrontend/viewer/src/locales/es.pofrontend/viewer/src/locales/fr.pofrontend/viewer/src/locales/id.pofrontend/viewer/src/locales/ko.pofrontend/viewer/src/locales/ms.pofrontend/viewer/src/locales/sw.pofrontend/viewer/src/locales/vi.pofrontend/viewer/src/stories/activity/previews.stories.sveltefrontend/viewer/src/stories/activity/summaries.stories.sveltefrontend/viewer/src/stories/editor/diff/diff-overview.stories.svelte
…eviews, sense numbers
Builds on the merge of develop (new Sense.pictures field + 5 new change types) and addresses the round-2 review feedback + CodeRabbit:
- Handle the 5 develop-added change types (sense pictures ×4 + set-main-publication) with proper summaries; configure the new Sense.pictures field.
- Create-entry commit no longer blows out the page when expanded (the collapse-to-editor wrapper gets min-w-0/min-h-0 — the spot the earlier grid fix missed).
- VLists lazy-load properly (itemSize set) so a 19-change commit no longer loads all 19 at once.
- Resolver: project only the columns it reads (no heavy JSONB hydration), load morph types lazily/projected; sense subjects disambiguate by number ("… › sense 2" / "gloss (2)") for empty/duplicate glosses.
- Styled before/after previews for all 7 vocab object types (was raw JSON), guarded by a preview-entityType coverage test.
- View-aware activity nouns (Lite "word/meaning" vs Classic), matching the field labels.
- Sense-creation collapse only fires on a pure sense tree (won't hide a mixed edit).
- CodeRabbit: created-semantic-domain summary keeps its name (not just code); homograph subscript shows with the Untitled placeholder too; clarifying comment on the ChangeTypes discriminator fallback (it intentionally mirrors Harmony — failing fast there breaks SetPartOfSpeechChange).
Tests: resolver targets/reorders/vocab subjects/sense numbers; frontend new facts, collapse purity, semantic-domain label; preview-entityType guard. i18n extracted + context added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-91bf15 # Conflicts: # frontend/viewer/src/locales/en.po # frontend/viewer/src/locales/es.po # frontend/viewer/src/locales/fr.po # frontend/viewer/src/locales/id.po # frontend/viewer/src/locales/ko.po # frontend/viewer/src/locales/ms.po # frontend/viewer/src/locales/sw.po # frontend/viewer/src/locales/vi.po
A delete change has a null snapshot, so the History button's context.snapshot?.id was undefined and clicking it did nothing. Fall back to context.previousSnapshot?.id (the pre-delete entity) so History works there too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e preview, layout Baseline checkpoint of the activity-view iteration before the design-review pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n dark mode Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…boxed data Subject renders as a bold borderless token (the scannable element); the verb phrase inherits a muted container colour; data values stay as bordered chips. Drops the middot separators — the bold subject plus its trailing margin is enough separation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Detailed now groups a commit's facts by subject — the headword shows once as a bold header, its changes listed beneath along an indent rail — instead of repeating the subject on every line. Gives the two list modes distinct jobs (skim vs inspect) rather than just different lengths. Adds groupBySubject + a hideSubject prop on ChangeSummary; keyed by subject string so distinct senses of one entry don't wrongly fold together. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Multi-select diffs merge before/after into one sorted list, so a removed item keeps its sorted position instead of trailing the kept ones (e.g. a removed semantic domain by code). - Audio-only example creates read as "with audio" instead of leaking the sil-media:// URI; firstAlternative now skips audio writing systems everywhere. - The example Reference field is framed like the text input it is (DiffShell) rather than rendered as bare text. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Any media value (audio-WS field or a sil-media:// value on any ws) now shows an audio marker — an i-mdi-volume-high icon plus "audio" — instead of leaking the media URI. Covers Set/Cleared field edits and audio-only example creates, and drops the unreadable audio ws code. First use of iconography in the summaries. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The collapsed create-entry preview rendered the entry's CURRENT (possibly since-edited) state. Now it assembles the entry as-of the commit from that commit's own change snapshots: entry + its senses + their examples, in commit order. Part of speech is resolved from the id when the snapshot carries only that; semantic-domain names come denormalized from the sense snapshot. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The resource-create/upload/delete commit now reads 'Added audio recording' with the audio marker, and its preview plays the recording. Two constraints drove this: the resource change carries no kind, and the field-set that would identify it as audio lives in a SEPARATE commit — so kind can't be derived cheaply (would need a file fetch). Audio is the only resource kind today, so we assume it (documented). The preview couldn't find the resource because RemoteResource isn't an IObjectWithId (null snapshot, entityType 'Unknown'); we now read the resource id off the raw change instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…/remove Empty audio delegates to AudioInput's own 'No audio' placeholder (matches the editor) instead of a blank. A replaced/removed recording now sits in a destructive-bordered surface above the added one in an emerald-bordered surface — a diff gutter reads better on a player than a line-through would. New surface-only palette tokens (bg tint, no text styling) for non-text diff content. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A deleted component link is gone from the projection, so 'Removed component' had no headwords. Recover its endpoints from the create change (AddEntryComponentChange carries both entry ids) — two steps: load the CFC's changes, read the ids off the create — enough to name the complex form (subject) and component (target). Frontend renders the component target on the remove branch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Detailed rows now carry a coloured gutter glyph per change (added +, removed −, changed ✎, reordered ↕) for fast visual grepping down a commit — Simple mode stays a plain-sentence skim. The per-row change cap goes 10→50 since Detailed is the inspect view; still bounded because a row isn't internally virtualised. New pure factCategory() (tested) keeps the mapping out of the template. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Custom-view previews now diff their field lists (entry/sense/example) and writing-system lists (vernacular/analysis) plus base — not just the name. A new vocab-diff-coverage test parses every vocab model's generated interface and asserts each property is either handled by DiffVocabPrimitive or explicitly ignored (vocab-diff-fields.ts) — so a new backend field can't silently vanish from the diff. The guard immediately caught WritingSystem.isAudio, now shown. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DiffSelect drew the transition with a literal Unicode → ; use i-mdi-arrow-right like the rest of the icon system for consistent sizing and alignment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CustomView.base and WritingSystem.type rendered raw enum values ('FwLite', '0'). Map them to the app's usual names — 'FieldWorks Lite'/'FieldWorks Classic' and 'Vernacular'/'Analysis' — keyed by both the string name and numeric value so it works whichever the wire sends; unknown values still fall through to the raw text.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sense numbers are the sense's 1-based position, shown when an entry has more than one sense — independent of the gloss (FieldWorks' rule; homograph numbers, which key off identical headwords, stay separate). Replaces the old empty/duplicate-gloss subscript logic: >1 sense → "N gloss" (or "N" when gloss-less); a lone sense → its gloss or nothing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The humanized change-type stripe restated in technical vocabulary what the plain-English summary and the diffs below already show — low value for end users. Removed it and the now-unused ProjectActivity.ChangeTypeLabels (ChangeTypes stays for filtering); regenerated types. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A commit that creates one sense plus its child examples now collapses to a single CollapsedSenseDiff (the sense and its examples, all-added) instead of a flat change list — the sense-level counterpart of the create-entry collapse. New assembleSenseAtCommit pieces the sense together from the commit's at-commit snapshots; entry-creation still takes precedence. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An at-a-glance 'who' signal: a person icon coloured from a stable hash of the author name, using a small curated palette that reads in both themes. Colours the icon only — the name stays default text, so readability is untouched (the concern with colouring the name itself). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-91bf15 # Conflicts: # frontend/viewer/src/lib/activity/ActivityView.svelte
Pulls the per-change preview's headword dropdown (open-in-new-window, go-to-browse) into its own component so the collapsed create views can reuse it. Behaviour-neutral. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Collapsed create-entry / create-sense cards now show the entry-headword menu button and Preview / Details tabs mirroring the per-change cards; Details renders the whole commit's change JSON. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The DiffVocabPrimitive base-view / field-group / writing-system labels were never extracted (a prior extract ran against the wrong working tree), failing the i18n CI check. Also refreshes source references after the EntryHeadwordButton / AuthorLabel extractions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nine errors in existing branch files (unused import, redundant assertion, enum-mirroring object keys vs naming-convention, base-to-string, forbidden inline import() type, mutable Map). Root-cause fixes, no rule suppression: enum label maps → guarded functions; merge helper → array ops; import type. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FieldWorks/System previously sat to the right of the name; put the new coloured person icon there too so all authors keep one side. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The change-kind glyph's fixed gutter is bullet-list styling that helps scan a group of siblings; on a lone change it just eats width. Reserve the gutter only for grouped rows and inline the glyph otherwise. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the gutter border and inline padding; let the surface background carry the diff signal so the audio player aligns with sibling fields. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Row-flex is items-center so the glyph sits with the text (was baseline with a nudge). Inline glyph uses align-middle. Meta line gets mt-2 to separate what happened from who/when. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Inline vertical-align:middle sat the glyph on the x-height baseline — visually low against lowercase text. Flex + items-center like the grouped case aligns it with the summary line. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The inline change-kind glyph on ungrouped rows becomes a small emerald (added) / destructive (removed) triangle in the top-right of the list item. Edits and reorders leave the corner bare — absence is the signal that this row is a modification. Multi-fact commits keep per-fact glyphs inline (a single corner can't represent a mixed set). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…view Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eted entities Small commits now list their facts under the entry header exactly like the same tree inside a bigger commit (no more one-line collapse), entity names render bold in every summary context (chips stay reserved for data values), and the Simple/Detailed list mode is replaced by the view picker. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-91bf15 # Conflicts: # frontend/viewer/src/locales/en.po # frontend/viewer/src/locales/es.po # frontend/viewer/src/locales/fr.po # frontend/viewer/src/locales/id.po # frontend/viewer/src/locales/ko.po # frontend/viewer/src/locales/ms.po # frontend/viewer/src/locales/sw.po # frontend/viewer/src/locales/vi.po
The Comments feature merged from develop adds six change types the activity coverage guards flagged: comments render as added/edited/deleted comment lines (thread creation is fact-less — its first comment carries the line), thread status reads as closed/reopened, and the two comment entity types opt out of a styled preview. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>



The activity list used to name the change type; it now says what actually happened — "Apfel › apple · Set Definition (en) to …", "Changed part of speech to Noun", "Removed semantic domain 5.2 Food" — in Simple and Detailed modes. The detail panel shows a real before/after diff of the entry (a read-only reuse of the editor layout), and a commit that only builds one entry collapses to a read-only entry view instead of a stack of per-change diff cards.
Summaries name their subject — entry headword (with homograph subscript), "headword › gloss" for a sense, a vocab object's name — and any item a change references only by id (the part of speech set, the semantic domain / component removed or linked). Those names are resolved in one batched pass per page, so naming adds no per-row queries. Large commits (bulk creates, big sync batches) collapse to one or a few lines ("Created 100 semantic domains") rather than one line per change.
The list of change types is generated from the backend via Reinforced.Typings, and coverage tests fail until every change type has a summary, every model field is configured (or explicitly excluded), and every create type has a bulk-collapse noun — so this can't silently rot as the model grows.
Screenshots
Sadly such low-res that they're not very useful 😆
Activity summaries:

Activity previews:

Test plan
svelte-checkclean;FwLiteSharedbuilds; generated types regeneratedactivity/summaries,activity/previews, collapsed entryNotes for reviewers
@sanity/diff-match-patchpowers the inline text diff.Added {noun}); correct in English and documented with translator context, but they won't inflect perfectly in gendered/noun-class languages. Deliberate tradeoff for terse log lines vs. a combinatorial msgid explosion — happy to expand if reviewers prefer.HistoryService.ProjectActivitynow materializes a page (the label resolver batch-loads across it); itsIAsyncEnumerablesignature could be tightened toTask<IReadOnlyList<…>>in a follow-up — all current consumers already materialize.pnpm-lock.yamlline converged during install onto a transitive peer set already present ondevelop; not a deliberate dependency change.🤖 Generated with Claude Code