Skip to content

refactor(web): unify per-type item field selection behind ItemDescriptor#68

Merged
allisson merged 1 commit into
mainfrom
improve-codebase
Jul 18, 2026
Merged

refactor(web): unify per-type item field selection behind ItemDescriptor#68
allisson merged 1 commit into
mainfrom
improve-codebase

Conversation

@allisson

Copy link
Copy Markdown
Owner

Problem

The "which fields matter for type X" decision was re-encoded in four places:

  • vault-list search switch (route.tsx, 44-line per-type switch)
  • getItemSubtitle (ItemCard.tsx)
  • read-only detail renderer ($itemId.tsx)
  • form field registry (item-fields/index.ts)

Search + subtitle carried as string casts (item type unknown at the call site) and neither was directly tested.

Change

Deepen the form registry with a pure, React-free sibling — ITEM_DESCRIPTORS in item-fields/descriptors.ts:

  • Each entry keyed by VaultItem['type'], typed against Extract<VaultItem, {type: K}>no casts inside entries.
  • Two dispatchers getItemSearchText / getItemSubtitle carry the one contained assertion and return '' for unknown types (preserves the old default safety).
  • Kept separate from index.ts so the hot list-filter path does not pull the field-editor component graph into its bundle.
  • getItemSubtitle re-exported from ItemCard.tsx → existing import paths + the trash test mock keep working (zero caller churn).

route.tsx swaps its 44-line switch for item.title... || getItemSearchText(item)....

Invariant (locked by test)

The search haystack is a plaintext index — toSearchText emits no secret material (password, number, cvv, secret, clientSecret, privateKey, passphrase, mnemonic) and excludes the generic title. A table-driven test asserts this across all 7 types + developer_credential×3 modes + crypto_wallet×2 + an unknown type.

Scope

Detail renderer (ReadComponent) and TypeFilter TYPE_OPTIONS are deliberately left as the obvious next descriptor fields (noted in CONTEXT.md).

Verification

  • descriptors.test.ts — 31/31 pass
  • web suite — 187/187 pass (trash mock resolves via re-export)
  • tsc -b clean · eslint clean

Net −76 / +15 in touched source; previously-untested search + subtitle logic now sits behind one tested interface.

🤖 Generated with Claude Code

The "which fields matter for type X" decision was re-encoded in four places:
the vault-list search switch (route.tsx), getItemSubtitle (ItemCard.tsx), the
read-only detail renderer, and the form field registry. Search and subtitle
carried `as string` casts because the item type was unknown at the call site,
and neither was directly tested.

Deepen the existing form registry with a pure, React-free sibling:
ITEM_DESCRIPTORS in item-fields/descriptors.ts. Each entry is keyed by
VaultItem['type'] and typed against Extract<VaultItem, {type: K}>, so field
access needs no casts; the two dispatchers (getItemSearchText,
getItemSubtitle) carry a single contained assertion and return '' for unknown
types. Kept separate from index.ts so the hot list-filter path does not pull
the field-editor component graph into its bundle. getItemSubtitle is
re-exported from ItemCard.tsx so existing import paths and the trash test mock
keep working.

Invariant, locked by a table-driven test: the search haystack emits no secret
material (password, number, cvv, secret, clientSecret, privateKey, passphrase,
mnemonic) and excludes the generic title. Detail renderer and TYPE_OPTIONS are
left for a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@allisson
allisson merged commit 9431a13 into main Jul 18, 2026
1 check passed
@allisson
allisson deleted the improve-codebase branch July 18, 2026 21:52
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.

1 participant