Skip to content

feat(web): expose validity in ChunkOut + render badge in viewer (Goal 7)#540

Merged
memtomem merged 1 commit intomainfrom
feat/temporal-validity-goal-7
Apr 29, 2026
Merged

feat(web): expose validity in ChunkOut + render badge in viewer (Goal 7)#540
memtomem merged 1 commit intomainfrom
feat/temporal-validity-goal-7

Conversation

@memtomem
Copy link
Copy Markdown
Owner

Summary

Final RFC §CLI/Web UI surface — temporal-validity window visible end-to-end in the Web UI.

Backend. ChunkOut schema gains valid_from_unix / valid_to_unix (both nullable, default None). chunk_to_out propagates from ChunkMetadata. Every chunk-shaped route response (list, get, search, similar) now carries the window.

Frontend. Compact Valid: 2025-08-15 → 2026-03-31 badge next to the existing created-time badge in the detail modal AND inside each search result item. Always-valid chunks (both bounds null) hide the badge entirely so the default UI stays compact for users without temporal-validity frontmatter.

Expired chunks (now > valid_to) get a muted opacity + line-through with a localized "Expired" tooltip. The chunk stays visible — validity is a search-time filter (Goal 4 stage), not a storage hide. The Web UI is a viewer; filtering happens upstream when the user issues mem_search/mm search with as_of.

i18n: two new keys (search.detail_validity_label "Valid"/"유효", search.detail_validity_expired "Expired"/"만료됨") in en + ko locale JSON. Date format itself is locale-independent (ISO-8601).

Drive-by fix

PATCH /api/chunks/{id}/tags reconstructed ChunkMetadata with an explicit eight-field list, silently dropping any field not enumerated. Goal 7 adds two more such fields (valid_from_unix, valid_to_unix), so the implicit-loss bug would have ridden along with this PR. Switched to the dict-spread copy-with-override pattern that mm add already uses (memory.py).

This also restores round-tripping for overlap_before/after, parent_context, file_context, language — silently lost on tag updates since the route was written. Test pins the regression so re-flattening is loud (test_tag_update_preserves_validity inspects the upsert_chunks await args directly because that's the wire the lost fields would have been dropped on).

Stack

This is the last of three stacked PRs implementing Goals 5+6+7:

  1. feat(server): expose as_of on mem_search MCP tool (Goal 5) #538 — Goal 5 MCP mem_search(as_of=...) (base: main)
  2. feat(cli): expose --as-of on mm search + Validity column on mm recall (Goal 6) #539 — Goal 6 CLI surfaces (base: feat(server): expose as_of on mem_search MCP tool (Goal 5) #538)
  3. this PR — Goal 7 Web UI badge (base: feat(cli): expose --as-of on mm search + Validity column on mm recall (Goal 6) #539)

After this lands, all 7 RFC goals are in main. Supersession (auto-detection / superseded_by traversal) is deferred to a v2 RFC per §Non-goals §2.

Test plan

  • ruff check + format
  • 3 new tests in test_web_routes.pyTestChunkValidityFields: chunkout includes validity when set / null when unset / tag-update preserves validity (regression for the drive-by fix)
  • i18n parity tests pass (ko gains the two new keys at the same line as en)
  • web/i18n/temporal regression: 123/123 pass; wider validity/web/search 668/668 pass
  • CI green
  • Manual smoke (post-merge to Goal 6 base): mm web → memory viewer, confirm badge renders + dates are correct + expired styling fires for past valid_to

🤖 Generated with Claude Code

@memtomem memtomem force-pushed the feat/temporal-validity-goal-6 branch from 5d858bd to 3e26a7c Compare April 29, 2026 04:19
@memtomem memtomem changed the base branch from feat/temporal-validity-goal-6 to main April 29, 2026 04:22
…r (Goal 7)

Final RFC §CLI/Web UI surface — the temporal-validity window is now
visible end-to-end. Backend exposure on ``ChunkOut`` (used by every
chunk-shaped route response: list, get, search results, similar)
threads ``valid_from_unix`` / ``valid_to_unix`` from chunk metadata.
Frontend renders a compact ``Valid: 2025-08-15 → 2026-03-31`` badge
next to the existing created-time badge in the detail modal and inside
each search result item. Always-valid chunks (both bounds null) hide
the badge entirely so the default UI stays compact for users who
haven't opted into temporal-validity frontmatter.

Expired chunks (``now > valid_to``) get a muted opacity + line-through
on the badge text, with a localized "Expired" tooltip. The chunk stays
visible — validity is a search-time filter concept (Goal 4 stage), not
a storage-level hide. The Web UI is a viewer; filtering happens
upstream when the user issues a search with ``as_of``.

Date display uses ``YYYY-MM-DD`` regardless of the original frontmatter
shape (date vs quarter), since the unix-second column does not preserve
that distinction. ``∞`` renders for unbounded sides — half-bounded
windows (only ``valid_from`` or only ``valid_to`` set) are first-class.

Drive-by fix in ``PATCH /chunks/{id}/tags``: the route reconstructed
``ChunkMetadata`` with an explicit eight-field list, silently dropping
any field not enumerated. Goal 7 adds two more such fields, so the
implicit-loss bug would have ridden along with this PR — switched to
the dict-spread copy-with-override pattern that the ``mm add`` CLI
already uses (memory.py). This also implicitly restores
``overlap_before/after``, ``parent_context``, ``file_context``, and
``language`` round-tripping through tag updates — those have been
silently lost since the route was written. Test pins the regression so
re-flattening is loud.

i18n: two new keys (``search.detail_validity_label``,
``search.detail_validity_expired``) added to en.json + ko.json
("Valid"/"유효", "Expired"/"만료됨") so the trust-UX surface stays
localized. Date format itself is locale-independent (ISO-8601 strict).

Tests (3 new, in test_web_routes.py at TestChunkValidityFields):

- ``test_chunkout_includes_validity_when_set``: chunk with both bounds
  populated (2024-12-15 → 2025-Q1 end) round-trips through
  ``GET /api/chunks/{id}`` with the unix integers intact.
- ``test_chunkout_validity_null_when_unset``: default ``_make_test_chunk``
  produces a chunk without validity → both fields serialize as ``null``
  so the frontend always-valid branch (hidden badge) fires.
- ``test_tag_update_preserves_validity``: regression for the
  drive-by fix — PATCH /chunks/{id}/tags must not drop validity (or
  ``parent_context`` / ``overlap_before`` for that matter). Inspects
  the ``upsert_chunks`` await args directly because that is the wire
  the silently-dropped fields would have been lost on.

Frontend rendering coverage is manual (no JS test harness in this repo);
the API contract test pins what the frontend reads from JSON.

This is the last code Goal in the RFC — no further surface work
planned. Goal-by-goal status table updated in the matching
memtomem-docs commit.

Co-Authored-By: Claude <[email protected]>
@memtomem memtomem force-pushed the feat/temporal-validity-goal-7 branch from ce6bed8 to 4b946a2 Compare April 29, 2026 04:23
@memtomem memtomem merged commit 5e66c4e into main Apr 29, 2026
7 checks passed
@memtomem memtomem deleted the feat/temporal-validity-goal-7 branch April 29, 2026 04:25
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 29, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants