Skip to content

feat(web): Index tab — surface hard-to-discover behaviors (static, 7-item bundle) (#579)#587

Merged
memtomem merged 8 commits intomainfrom
feat/579-index-tab-static-surfacing
Apr 30, 2026
Merged

feat(web): Index tab — surface hard-to-discover behaviors (static, 7-item bundle) (#579)#587
memtomem merged 8 commits intomainfrom
feat/579-index-tab-static-surfacing

Conversation

@memtomem
Copy link
Copy Markdown
Owner

Closes #579.

Summary

Bundles the 7 static-surfacing items from issue #579 into one PR — text/CSS/placeholder/cursor changes only, no policy decisions, no schema breaking. PR #575 unified the Index tab's 3 modes behind a segmented toggle but left several backend behaviors (one-shot vs persistent, where files land, what the Extensions hint means) silent. This bundle fills those gaps.

One commit per item so reviewers can step through; squash merge produces one release-notes line.

Commits

  1. feat(web): Index folder panel — surface one-shot vs persistent — inline hint at the top of the folder panel ("One-shot scan. For automatic re-indexing, use Sources → Add path.") + matching action button on the success toast that navigates to Sources tab and focuses the Add path button.
  2. feat(web): Compose — surface target file — placeholder shows the actual default save location with today's date (~/.memtomem/memories/2026-04-30.md (leave blank for auto)); add success toast echoes the saved path. Removes deprecated toast.saved_n_indexed key.
  3. feat(web): Upload — echo saved path in success toast — adds path: str | None to UploadFileResult (additive, default None — no breaking change), upload route returns the resolved dest (post-collision-rename), toast echoes it.
  4. feat(web): header sys-info chip jumps to Settings → Configprovider/model · backend chip becomes clickable (cursor + hover + focus-visible), Enter/Space activates, lands on Settings → Config.
  5. style(web): Compose textarea — double the default heightmin-height: 120 → 240px.
  6. style(web): Index Extensions hint — drop callout chrome, add ⓘ glyph#index-config-hint ID-specific override stripping background/border/padding; ⓘ via CSS ::before (single source of truth in stylesheet, regardless of writer). Other .config-hint users (#home-config-info, #ns-config-info) untouched.
  7. copy(web): Compose target file label — clarify "save location"Target FileTarget file (save location) / 대상 파일 (저장 위치).

Risk handling

These were flagged before coding; addressed in the commits above.

  • i18n.applyDOM rewrites textContent on every lang toggle and would strip a nested <a> from a single data-i18n paragraph. Commit 1 splits the hint into sibling <span> + <a> + <span> so the link element survives toggles.
  • Lang-toggle {date} token leak in the Compose placeholder. Commit 2 splits the i18n value into ..._prefix + ..._suffix strings and removes data-i18n-placeholder from the input — JS now owns the placeholder and refreshes it on langchange. The {date} token never appears in user-visible text.
  • Path tildify drift (server returns absolute $HOME paths the browser can't resolve). Commit 2 adds a tildifyPath() helper that anchors on the well-known .memtomem/ segment used by backend write code; commit 3 reuses it.
  • #index-config-hint ⓘ prefix SoT — moved to CSS ::before so the marker is single-source-of-truth regardless of which writer populates the element.

Test plan

Automated (passing):

  • uv run ruff check packages/memtomem/src + ruff format --check — clean
  • uv run pytest -m "not ollama" — 3259 passed
  • i18n parity (tests/test_i18n.py) — 12/12 passed; 6 new keys present in both en.json + ko.json (index.folder_oneshot_hint / _link / _suffix, index.add_file_placeholder_prefix / _suffix, toast.action.register_persistent, toast.saved_to_file, toast.upload_complete_with_path, header.sys_info_jump_title); 1 updated (index.add_file_label); 1 removed and dropped from required-set (toast.saved_n_indexed)

Manual (Index tab smoke — uv run mm web):

  • Folder mode: hint visible above Path input; clicking the inline link switches to Sources tab and focuses the "+ Add path" button. After a successful Index, the toast shows a "Register as Source" action that does the same.
  • Compose mode: placeholder reads ~/.memtomem/memories/{today}.md (leave blank for auto) with today's actual date (e.g. 2026-04-30). Add success toast echoes the saved file path under ~/.memtomem/memories/….
  • Upload mode: after upload, toast echoes the saved path under ~/.memtomem/uploads/… (or first non-error file's path).
  • KO toggle (regression check): cycle KO ↔ EN twice, then check Compose placeholder shows today's date with no {date} token leak; folder hint link element still present and clickable; header chip's tooltip still includes the localized "Open Embedding & Storage settings" line.
  • Header chip: hover shows pointer cursor; click jumps to Settings tab and activates Config section. Enter/Space on focus does the same.
  • Visual: Compose textarea is roughly twice as tall (240px vs 120px); #index-config-hint is a plain muted line with leading ⓘ (no box/border/background); home/namespace hints unchanged.
  • Dark + light themes both render the new hint text and link color legibly.

Out of scope (per #579)

🤖 Generated with Claude Code

pandas-studio and others added 8 commits April 30, 2026 11:56
The folder Index button runs a one-shot scan; persistent re-indexing
requires registering the path under Sources. PR #575 unified the Index
tab modes but left this difference silent — users who wanted automatic
re-indexing had no in-panel cue that they should look elsewhere.

Add an inline hint at the top of the folder panel ("One-shot scan. For
automatic re-indexing, use Sources → Add path."). The link element is
split out as a sibling of the help text so the i18n updater (which
overwrites textContent on every lang toggle) can't strip the click
handler from a nested anchor.

The Index success toast also gains a "Register as Source" action button
that runs the same Sources-tab navigation helper, so the cue is reachable
both before and after the scan.

Co-Authored-By: Claude <[email protected]>
Make the Compose mode's `Target file` semantics visible in two places:

- Placeholder: previously the generic `path/to/memories.md`. Now
  `~/.memtomem/memories/2026-04-30.md (leave blank for auto)` — the
  default location backend code in `add_memory()` already uses, with
  today's date computed at load time so the cue stays accurate.

- Add success toast: previously "Saved — N chunks indexed", agnostic to
  where the content went. Now "Saved to ~/.memtomem/memories/…/file.md
  — N chunks indexed", echoing the actual path returned by the API.

The placeholder is split into i18n prefix + `{date}` runtime + i18n
suffix to dodge the lang-toggle path: i18n.applyDOM rewrites
`data-i18n-placeholder` textContent on every switch and would otherwise
clobber a JS-templated value back to the raw `{date}` token. The input
keeps a static placeholder fallback (`~/.memtomem/memories/`) for the
sub-second window before settings-config.js's helper runs.

The toast path-tildify helper (`tildifyPath`) collapses the
host-specific `$HOME` prefix the server sends back to `~`. The browser
doesn't know `$HOME`, so the helper anchors on the `.memtomem/` segment
that backend write paths consistently use. Reused in #4.5a.

The deprecated `toast.saved_n_indexed` key is removed from both locales
(unused after this commit) and dropped from the i18n required-set test.

Co-Authored-By: Claude <[email protected]>
Upload silently writes to ~/.memtomem/uploads/, then on collision
suffixes the destination filename with the existing file's mtime
(system.py:862). Until now the success toast only said how many chunks
were indexed; users had no way to see where the file actually landed,
or that a rename had occurred.

Add `path: str | None = None` to UploadFileResult and have the upload
route echo the resolved `dest` (post-rename) so the client doesn't have
to guess. The toast prefers the new path-aware key
(`toast.upload_complete_with_path` — "Indexed N chunks → saved to
~/.memtomem/uploads/foo.md"), falling back to the count-only message if
no path is present (e.g., all-error batch). The path-tildify helper
introduced in the previous commit collapses $HOME to ~ for display.

The schema change is purely additive (new optional field, default
None), so existing callers and tests continue to work unchanged.

Co-Authored-By: Claude <[email protected]>
The "provider/model · backend" chip in the header is the only place
that surfaces the embedding pipeline and storage backend on every tab.
Users debugging "why is search slow" or "why are embeddings cleared"
have to recall that the matching settings live under Settings → Config
and click through manually.

Make the chip clickable: cursor changes on hover/focus, click +
keyboard activation (Enter/Space) navigate to Settings tab and select
the Config section. The tooltip now ends with "Open Embedding & Storage
settings" (i18n'd) and an aria-label exposes the same hint to screen
readers; both refresh on language toggle alongside the existing
detail lines (provider/model/dim, backend).

Co-Authored-By: Claude <[email protected]>
5 rows is enough to write a tag list, not enough to write the kind of
freeform memo people actually paste into Compose. Lift `min-height`
from 120 to 240 px (≈10 rows) so the textarea matches a real writing
surface; users can still drag the resize handle in either direction.

Co-Authored-By: Claude <[email protected]>
…579)

The Extensions/max-chunk hint at the bottom of the folder panel is read
as data (current config values), not as a callout that demands the
user's attention. Inheriting the shared .config-hint background +
border made it look heavier than the surrounding inputs.

Override the styling for `#index-config-hint` only (the home and
namespace hints — `#home-config-info`, `#ns-config-info` — keep their
existing chrome) to a plain muted line, and add the ⓘ marker via CSS
`::before` so it's single-source-of-truth in the stylesheet regardless
of which writer populates `textContent`.

Co-Authored-By: Claude <[email protected]>
"Target file" alone reads as if it could be a path on disk, on the
filesystem, or a virtual id — the user has no anchor for what's being
asked of them. Append "(save location)" / "(저장 위치)" to the label so
the placeholder hint and saved-path toast (introduced earlier in this
bundle) reinforce the same idea: this input controls where the memory
is stored under ~/.memtomem/memories/.

Markup unchanged — just an i18n string update in both locales.

Co-Authored-By: Claude <[email protected]>
Several JS-owned dynamic strings populate via listeners — Compose
placeholder (settings-config.js _refreshAddFilePlaceholder) and the
header-chip aria-label / jump-hint title (_syncHeaderConfig) — read
t() at the moment they run. settings-config.js's module-level
fetchServerConfig() can race ahead of app.js's `await I18N.init()`, so
those listeners can fire before the locale cache is populated and end
up writing the raw i18n key (e.g. `index.add_file_placeholder_prefix`)
to user-visible UI. Surfaced by the smoke for #579: the Compose
placeholder showed `index.add_file_placeholder_prefix2026-04-30
index.add_file_placeholder_suffix` and the header chip's aria-label
held `header.sys_info_jump_title` until the user toggled language.

Have I18N.init() dispatch a single langchange event after the locale
cache is filled. The same listeners then re-read t() with real
translations and overwrite their stale raw-key state. The bumped query
string on i18n.js (?v=2 → ?v=3) busts browser disk cache so the new
init body actually loads.

Co-Authored-By: Claude <[email protected]>
@memtomem
Copy link
Copy Markdown
Owner Author

Smoke complete (Playwright)

Manual smoke now done — found one regression, fixed in 699557f and pushed.

Regression: raw i18n keys leaked at first paint

After mm web restart and clean Playwright session, two JS-owned strings showed raw keys until the user toggled language:

  • Compose placeholder: index.add_file_placeholder_prefix2026-04-30index.add_file_placeholder_suffix
  • Header chip aria-label / tooltip last line: header.sys_info_jump_title

Cause: settings-config.js module-level fetchServerConfig() can race ahead of app.js's await I18N.init(), so _refreshAddFilePlaceholder and _syncHeaderConfig listeners can run with the locale cache still empty and call t() → raw-key fallback.

Fix: have I18N.init() dispatch a single langchange event after the locale cache is filled. The same settings-config.js listeners re-read t() with real translations and overwrite their stale state. ?v=2?v=3 on the i18n.js script tag busts disk cache so the new init body actually loads.

Smoke matrix (all passing after fix)

Check Result
Initial Compose placeholder ~/.memtomem/memories/2026-04-30.md (비워두면 자동)
Initial header chip aria-label 임베딩 & 저장소 설정 열기
Initial header chip title Embedding: onnx/bge-m3 (1024d) / Storage: sqlite / 임베딩 & 저장소 설정 열기
Folder hint link click tab-sources active, memory-add-path-btn focused ✅
Header chip click tab-settings active, config section selected ✅
Header chip Enter (keyboard) same as click ✅
KO ↔ EN round-trip folder link element survives, no {date} token leak in placeholder ✅
Compose textarea min-height 240px ✅
#index-config-hint styling background-color: rgba(0,0,0,0), border-top-width: 0px, padding: 0px, ::before { content: "ⓘ " }
#index-config-hint hidden when no data served hidden attribute respected ✅

Note on PR scope

This adds an 8th commit to a bundle the issue body specifies as 7 commits. The fix is wholly in service of the same 7 surfacing items and is needed for them to actually work as intended at first paint, so it lives here rather than as a separate PR. Squash merge still produces one release-notes line.

@memtomem memtomem merged commit 1d7ee66 into main Apr 30, 2026
7 checks passed
@memtomem memtomem deleted the feat/579-index-tab-static-surfacing branch April 30, 2026 03:23
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 30, 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.

feat(web): Index tab — surface hard-to-discover behaviors in one bundle (static surfacing)

2 participants