Skip to content

Fix stale cached title on tab titles, query views, and collections - #2179

Merged
mstomar125 merged 2 commits into
mainfrom
worktree-issue-2012-stale-title
Aug 20, 2026
Merged

mstomar125 merged 2 commits into
mainfrom
worktree-issue-2012-stale-title

Conversation

@mstomar125

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #2005 / #2011, which fixed the stale cached title in BaseNodeViewer's header but deliberately scoped itself there. sharedNodeStore's cached title only refreshes via a backend round-trip, while optimistic content updates patch content and spread title forward untouched — so any surface still preferring a plain node.title || node.content kept showing a title computed for an earlier, possibly differently-typed state (most visibly, tab titles).

  • Adds 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.
  • Adds PluginRegistry.resolveDisplayTitle(node), which applies that rule using findSlashCommand(nodeType)?.hasTitleTemplate as the title-template signal (the same flag node-row.svelte already uses for this purpose). getNodeTitle (which backs tab titles via computeTabTitle, and additionally applies a plugin's getTitle override, e.g. date's "Today"/"Tomorrow") now delegates its non-override fallback branch to resolveDisplayTitle too.
  • Fixed call sites, all now routed through resolveDisplayTitle (or, for tab titles, getNodeTitle): plugin-registry.ts's getNodeTitle, list-view.svelte, kanban-view.svelte, table-row.svelte (both the content column and the row tooltip), collections.svelte.ts's selectedCollectionMembers.
  • Query views and the collections sidebar deliberately call resolveDisplayTitle rather than getNodeTitle, so they don't inherit a type's getTitle override — those surfaces show plain node text, not a tab-specific title, and (concretely) a date node's id is only guaranteed to parse as a real date in production, not in every synthetic id these surfaces might see.

Closes #2012

Test plan

  • New regression tests reproducing the exact stale-title scenario (title captured before a type conversion, content since changed) for list-view.svelte, kanban-view.svelte, table-row.svelte, and collections.svelte.ts's member list, each also asserting a title_template-driven type still shows its computed title.
  • New unit tests for resolveTitleOrContent and PluginRegistry.resolveDisplayTitle/getNodeTitle.
  • Updated plugin-registry.test.ts's pre-existing getNodeTitle test that had encoded the old (buggy) title-wins-over-content behavior.
  • bun run quality:fix clean (eslint + svelte-check, 0 errors/warnings).
  • Full frontend suite green (bun run test: 226 files / 4763 tests, 0 failures — two flakes seen under parallel contention, core-plugins.test.ts's viewer-resolution timeout and a perf-benchmark threshold, both confirmed pre-existing and unrelated by re-running in isolation).
  • bun run test:all (frontend + skill + Rust + e2e + Tauri-seam integration) green via the pre-push gate — all passed before this branch was pushed.

🤖 Generated with Claude Code

mstomar125 and others added 2 commits August 20, 2026 11:04
…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>
…down 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>
@mstomar125

Copy link
Copy Markdown
Contributor Author

Adversarial review complete. Two real findings, both fixed in 117da3d:

  1. SHOULD-FIX: collections.svelte.ts ran stripMarkdown unconditionally over resolveDisplayTitle's return value, including on the title_template branch — a computed title containing _/*/backtick (e.g. a name with an underscore) would get silently mangled. resolveDisplayTitle now takes an optional formatContent callback applied only when the resolved value came from content.
  2. SHOULD-FIX: resolveTitleOrContent's "is this title resolved" guard used ASCII-only \w, so a fully-resolved title in a non-Latin script (CJK, Arabic, ...) was misclassified as unresolved and rendered empty — a pre-existing Prefer content over cached title in viewer header for non-template types (closes #2005) #2011 guard this PR had just propagated from one call site to six. Switched to a Unicode-aware [\p{L}\p{N}] test.

Both fixes have regression tests. Full frontend suite green (226 files / 4767 tests), quality:fix clean, and the pre-push gate (frontend + Rust + e2e + Tauri-seam integration tests, including real local-model inference) passed before this branch was pushed.

@mstomar125
mstomar125 merged commit cd08ec7 into main Aug 20, 2026
@mstomar125
mstomar125 deleted the worktree-issue-2012-stale-title branch August 20, 2026 09:53
mstomar125 added a commit that referenced this pull request Aug 20, 2026
mstomar125 added a commit that referenced this pull request Aug 20, 2026
…yTitle

Merging main pulled in #2179 (Fix stale cached title on tab titles, query
views, and collections), which added PluginRegistry.resolveDisplayTitle()
and made getNodeTitle() delegate to it. Both derive hasTitleTemplate via
findSlashCommand(nodeType)?.hasTitleTemplate — the exact field this PR's
earlier commit stopped populating when it emptied createPluginFromSchema's
slashCommands array. Combined, every title_template-driven user-defined
schema type would have shown a blank/stale title instead of its computed
name across node-row.svelte, resolveDisplayTitle's consumers (query list/
kanban/table views, the collections sidebar), and getNodeTitle's consumers
(tab titles).

Moves hasTitleTemplate/titleTemplate off SlashCommandDefinition and onto
PluginDefinition as top-level fields (alongside deletableViaBackspace),
since the signal is a per-type property, not tied to slash-command
availability. Adds PluginRegistry.hasTitleTemplate()/getTitleTemplate()
accessors mirroring the acceptsContentMerge()/deletableViaBackspace()
pattern, and updates every consumer (resolveDisplayTitle, node-row.svelte,
base-node-viewer.svelte's now-dead custom-entity-slash-command branch) to
read from them instead of findSlashCommand(...)?.hasTitleTemplate.

createPluginFromSchema now sets hasTitleTemplate/titleTemplate at the
plugin level. Updates the 5 test files that constructed synthetic plugins
with these fields nested in a slash command, adds direct coverage for the
new accessors, and restores the two schema-plugin-loader.test.ts titleTemplate
assertions that had collapsed into duplicate bodies (both previously only
checked slashCommands is empty, losing coverage of hasTitleTemplate/
titleTemplate propagation).

Also fixes stale doc comments (core_schemas.rs, capture_service.rs,
agent_session_service.rs, agent_session_service.proto) that described
ai-chat nodes as created via the now-removed `/ai-chat` slash command, and
drops two GitHub issue-number references from comments this PR was
already touching.

Co-Authored-By: Claude <noreply@anthropic.com>
mstomar125 added a commit that referenced this pull request Aug 20, 2026
…pes) (#2181)

* Remove entity-type slash commands (/ai-chat, /person, user-defined types)

core#2133 established that slash commands are for content primitives plus
task; entity types are not slash-creatable. core#2151 documented three
surfaces that predated the rule as known exceptions, explicitly not
precedent. This retires them:

- aiChatNodePlugin and personNodePlugin now register slashCommands: []
  while keeping their viewer/reference/schema-form/backspace-guard
  registrations intact.
- createPluginFromSchema no longer generates a slash command for
  user-defined schema types; the plugin registration still exists for
  reference/viewer resolution.
- PLUGIN_PRIORITIES is removed from schema-plugin-loader.ts — CUSTOM_ENTITY
  was its only consumer, and CORE/USER_COMMAND were already unreferenced
  anywhere in the codebase.
- Inverted the five schema-plugin-loader.test.ts assertions that expected
  a generated slash command; updated core-plugins.test.ts and
  slash-command-service-registry-integration.test.ts command counts
  (13 -> 11 core commands).
- Deleted the now-resolved "known exceptions" paragraph from
  core-plugins.ts's slash-command doc comment.

Verified via an isolated dev:browser + real daemon stack that person,
ai-chat, and a fresh user-defined schema (Venue) all still create
instances through the sidenav/sidebar surfaces after their slash
commands are gone, and that the slash-command dropdown offers only the
11 primitive+task commands.

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix title_template regression from merging with #2179's resolveDisplayTitle

Merging main pulled in #2179 (Fix stale cached title on tab titles, query
views, and collections), which added PluginRegistry.resolveDisplayTitle()
and made getNodeTitle() delegate to it. Both derive hasTitleTemplate via
findSlashCommand(nodeType)?.hasTitleTemplate — the exact field this PR's
earlier commit stopped populating when it emptied createPluginFromSchema's
slashCommands array. Combined, every title_template-driven user-defined
schema type would have shown a blank/stale title instead of its computed
name across node-row.svelte, resolveDisplayTitle's consumers (query list/
kanban/table views, the collections sidebar), and getNodeTitle's consumers
(tab titles).

Moves hasTitleTemplate/titleTemplate off SlashCommandDefinition and onto
PluginDefinition as top-level fields (alongside deletableViaBackspace),
since the signal is a per-type property, not tied to slash-command
availability. Adds PluginRegistry.hasTitleTemplate()/getTitleTemplate()
accessors mirroring the acceptsContentMerge()/deletableViaBackspace()
pattern, and updates every consumer (resolveDisplayTitle, node-row.svelte,
base-node-viewer.svelte's now-dead custom-entity-slash-command branch) to
read from them instead of findSlashCommand(...)?.hasTitleTemplate.

createPluginFromSchema now sets hasTitleTemplate/titleTemplate at the
plugin level. Updates the 5 test files that constructed synthetic plugins
with these fields nested in a slash command, adds direct coverage for the
new accessors, and restores the two schema-plugin-loader.test.ts titleTemplate
assertions that had collapsed into duplicate bodies (both previously only
checked slashCommands is empty, losing coverage of hasTitleTemplate/
titleTemplate propagation).

Also fixes stale doc comments (core_schemas.rs, capture_service.rs,
agent_session_service.rs, agent_session_service.proto) that described
ai-chat nodes as created via the now-removed `/ai-chat` slash command, and
drops two GitHub issue-number references from comments this PR was
already touching.

Co-Authored-By: Claude <noreply@anthropic.com>

* Address round-2 review: remove dead entity-slash-command branch, fix Unicode/empty-template edge cases

- base-node-viewer.svelte: delete handleCustomEntitySlashCommand and its two
  rendersAsEntityRow(newNodeType) call sites — unreachable now that no
  first-party plugin's slash command ever targets an entity-row type
  (ai-chat, person, and all user-defined schema types have slashCommands: []
  after this PR). No production registration path can still reach it, so
  per this repo's no-dead-code convention it's deleted rather than kept as
  latent "extension point" infrastructure.
- node-row.svelte: use the same Unicode-aware HAS_RESOLVED_CHARACTER_RE
  (exported from node-display-title.ts) that resolveDisplayTitle's
  consumers use, instead of the ASCII-only /\w/ — a title_template
  resolving to a non-Latin-script name (CJK, Arabic, Cyrillic, ...) now
  renders correctly here too instead of falling back to the raw template
  placeholder.
- plugin-registry.ts: getTitleTemplate() now gates on hasTitleTemplate()
  instead of titleTemplate's own truthiness, so a plugin with
  titleTemplate: '' (which hasTitleTemplate correctly treats as "no
  template") can't return a defined-but-empty string, matching the
  method's documented "undefined means no template" contract.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

Tab title and query views show stale cached title instead of current content

1 participant