Repository navigation
Prefer content over cached title in viewer header for non-template types (closes #2005) - #2011
Conversation
…pes (closes #2005) BaseNodeViewer's unfocused header read `title || content`, unconditionally preferring the store's cached `title` for every node type. That cache is only refreshed by a backend round-trip while optimistic content updates leave it untouched, so a title computed for a transient state — e.g. `strip_markdown("/")` for a node still typed `text` mid-slash-command — survived the later conversion to `task` and showed as `/` whenever the header lost focus. Restrict the title preference to title_template-driven schemas, which is what the original comment already described as the intent: there `title` is computed from properties and is genuinely distinct from `content`. For every other type `content` is the source of truth, matching the focused and inline render paths. Template types get no fallback to `content` — an unresolved title renders empty so the read-only header shows its titleTemplate placeholder, consistent with node-row.svelte's inline rendering. The logic moves into `computeHeaderDisplayValue` in node-type-predicates.ts so it is directly unit-testable rather than mirrored by a test harness. Co-Authored-By: Claude <noreply@anthropic.com>
Pragmatic Code Review — PR #2011Review Type: Initial Review (verified: 0 prior reviews, 0 comments) Scope: 3 files, 1 commit ( SummaryA well-targeted, genuinely minimal fix. The diagnosis in the issue is correct and the chosen remedy (Option 2) is the right one: it removes a dependency on a cache being fresh rather than adding machinery to keep that cache fresh, which is the structurally simpler of the two options and eliminates a class of bug rather than one instance. Extracting I independently verified all four claims flagged for scrutiny. Three hold up. One ( Net effect on code health is clearly positive. Recommending APPROVE, with one suggestion worth folding in before merge and two follow-ups. Requirements Check (issue #2005)
Findings🟡 Important:
|
malibio
left a comment
There was a problem hiding this comment.
Automated pragmatic-code-review: APPROVE (posted as a comment — GitHub blocks self-approval). Full findings in the review comment above.
Folds in the review's suggested consistency fix plus the related test gap.
- Treat a template-computed title with no word characters as unresolved, so
the header falls back to its titleTemplate placeholder. node-row.svelte
guards the same value with /\w/ when rendering these nodes inline; the bare
falsy check here meant a title like " " (a "{first_name} {last_name}"
template with both fields empty) rendered blank with no placeholder —
breaking the very header/inline invariant this PR asserts.
- Scope markdown-header stripping to the content branch. A template-built
title is assembled from property values, so a leading '#' is literal text
rather than header syntax to strip.
- Document that hasTitleTemplate is custom-schema-scoped by construction,
while the backend's compute_title() applies a title_template to any schema
that has one. The two agree only because no core type ships a template
today; the note records the coupling so changing either side is discoverable.
- Also drop the docstring's "same rule as formatTabTitle" claim: that helper
additionally takes the first line, trims, and truncates. Only the strip
regex is shared.
Skipped, with reasoning:
- Moving computeHeaderDisplayValue beside formatTabTitle (or extracting a
shared stripMarkdownHeading). The reviewer marked placement acceptable as-is
and conditioned the move on touching the code again; doing it now widens the
diff past the bug fix for no behavior change. Worth doing when either
stripping site is next edited.
- A component-level test of the base-node-viewer wiring. Explicitly accepted
by the reviewer as a much larger lift; the extraction already removed the
mirrored-harness failure mode.
Verified the two new tests fail against the prior helper and pass with it.
Co-Authored-By: Claude <noreply@anthropic.com>
Address-Review SummaryCommit ✅ Addressed (3)1. Whitespace-only template title (the reviewer's "fold in first" item) 2. Markdown-strip scoped to the content branch (test gap 2) 3. Also corrected an inaccuracy in my own docstring found while verifying: it claimed the strip was the "same rule as ⏭️ Skipped (2)Moving Component-level test of the base-node-viewer wiring — explicitly accepted by the reviewer as a much larger lift; the extraction already removed the mirrored-harness failure mode that motivated the concern. 🧪 Verification
📋 Follow-up filed#2012 — the sibling stale-title surfaces ( Re-Review DecisionNO RE-REVIEW NEEDED. Three narrow edits to a single pure function plus two comments — no architectural change, no new functionality, no refactoring. Each behavior change is test-pinned against the prior version, the full suite is green, and both skips were pre-authorized by the review itself. |
…2179) * Fix stale cached title on tab titles, query views, and collections (#2012) Follow-up to #2011: sharedNodeStore's cached `title` only refreshes via a backend round-trip, while optimistic content updates patch `content` and spread `title` forward untouched. Several surfaces preferred a plain `node.title || node.content`, so they kept showing a title computed for an earlier, possibly differently-typed state. Introduces `resolveTitleOrContent` (lib/utils/node-display-title.ts) as the single shared rule: prefer `title` only when the node's schema is title_template-driven, `content` otherwise. `computeHeaderDisplayValue` (node-type-predicates.ts) now delegates to it instead of re-deriving the rule. `PluginRegistry` gains `resolveDisplayTitle(node)`, which applies the rule using `findSlashCommand(nodeType)?.hasTitleTemplate` as the title-template signal (the same flag node-row.svelte already uses for this), and `getNodeTitle` (tab titles) now delegates its non-plugin-override fallback to it too. Call sites fixed to route through `resolveDisplayTitle`: plugin-registry's getNodeTitle (backs tab titles via computeTabTitle), list-view.svelte, kanban-view.svelte, table-row.svelte (both the content column and the row tooltip), and collections.svelte.ts's selectedCollectionMembers. Query views and the collections sidebar deliberately use resolveDisplayTitle rather than getNodeTitle, so they don't pick up type-specific getTitle overrides (e.g. the date plugin's "Today"/"Tomorrow" formatting) that only make sense for a tab's specific contract. Adds regression coverage for the exact stale-title scenario (title captured before a type conversion, content since changed) across all fixed surfaces, plus unit tests for resolveTitleOrContent and resolveDisplayTitle. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Address review: Unicode-aware unresolved-title guard, scope stripMarkdown to content resolveTitleOrContent's "is this title resolved" guard used ASCII-only \w, so a title_template value fully resolved in a non-Latin script (CJK, Arabic, ...) was misclassified as unresolved and rendered empty. This PR had just propagated that guard from one call site (BaseNodeViewer's header) to six (tab titles, list/kanban/table views, collections). Switch to a Unicode-aware "any letter or number" test. collections.svelte.ts wrapped resolveDisplayTitle's return value in stripMarkdown unconditionally, including on the title_template branch — unlike the old node.title || stripMarkdown(node.content), which only ever stripped the content fallback. A computed title containing an underscore, asterisk, or backtick (property values, not markdown) would get silently mangled. resolveDisplayTitle now takes an optional formatContent callback applied only when the resolved value came from content. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Closes #2005
Problem
BaseNodeViewer's unfocused header readtitle || content, unconditionally preferring the store's cachedtitlefor every node type. That cache is only refreshed by a backend round-trip, while optimistic content updates leave it untouched — so a title computed for a transient state survived indefinitely.The traced repro: typing
/fires a normal content update while the node is still typedtext; if the debounced persist round-trips before the slash command is selected, the backend legitimately computesstrip_markdown("/")→"/". The user then picks "task" and types a real title, butupdateNode()patchescontentonly, spreading the staletitleforward. Result: the header shows/when unfocused and the correct value the moment it's focused.Confirmed not a data bug —
contentandtitleare both correct and identical in the database.Fix
Option 2 from the issue. The title-preference is now gated on
schemaFormLoader.hasTitleTemplate— the same signal that already governs that input'sreadonly, placeholder, andoninput/onfocusbehavior — so.titlewins only where it is genuinely a property-computed value distinct from content. Everywhere else.contentis the source of truth, matching the focused and inline (node-row.svelte) render paths.This matches what the original comment already described as the intent ("Prefer computed title (from title_template)…"); the bug was that it applied to every type rather than only template-driven ones.
Two details worth reviewer attention:
contentfallback for template types. For a titleTemplate schema an unresolved title now renders empty, so the read-only header shows its titleTemplate placeholder — consistent withnode-row.svelte, which also falls back to the template string rather than to content. The oldtitle || contentchain would have leaked raw content in that window.computeHeaderDisplayValueinnode-type-predicates.ts. The established pattern for BaseNodeViewer-adjacent logic is a test harness that mirrors the code, but a mirrored copy can stay green while the component regresses — extracting makes the test exercise the real path.Acceptance criteria
titleTemplate-driven types — computed title still displays when unfocusedbun run test:all, exit 0)bun run quality:fixcleanTesting
Baseline 4439 tests → 4445 (6 new).
test:allexit 0: unit 191 files / 4445 tests, browser 24, Rust all green.Verified the new tests genuinely catch the bug by reverting the helper to the old
title || contentlogic — 3 failures, including the exact/→ "Another Task" case — then passing with the fix.Caveat on criterion 3: the traced sequence is pinned as unit tests against the extracted logic, not driven through the running app. The logic is covered exactly; live UI confirmation was not performed.
Out of scope
The same stale-title preference exists on sibling surfaces reading the same store — notably
pluginRegistry.getNodeTitle()(plugin-registry.ts:469,node.title || node.content), which backscomputeTabTitle, so the tab title for the reproducing node shows the same stale/. Query list/kanban/table views share the pattern. Left untouched per the issue's Non-Goals; likely worth a follow-up issue.🤖 Generated with Claude Code