Skip to content

chore(tabs): remove the home-tab constructor, keep the sentinel - #482

Closed
PathGao wants to merge 1 commit into
feat/format-shortcuts-and-ctrl-tfrom
chore/drop-dead-home-tab-constructor
Closed

chore(tabs): remove the home-tab constructor, keep the sentinel#482
PathGao wants to merge 1 commit into
feat/format-shortcuts-and-ctrl-tfrom
chore/drop-dead-home-tab-constructor

Conversation

@PathGao

@PathGao PathGao commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Prerequisite

Merge #480 first. #480 is what makes addHomeTab dead — it points the
document-level Ctrl+T at handleNewFile, and that branch was the method's only
caller. So this PR is branched on feat/format-shortcuts-and-ctrl-t and targets
it, which keeps the diff above to this change alone (1 commit, 10 files). GitHub
retargets the base to master on its own once #480 lands; no rebase needed
unless #480 itself changes.

What this is

TabManager.addHomeTab builds a tab whose path is the HOME sentinel rather
than a file. After #480 nothing in the app calls it — only tests do.

It is not merely uncalled. Every other part of the tab pipeline already treats a
home tab as something to get rid of:

stage what it does with the sentinel
serializeState filters on hasRealFilePath — never writes it (#401)
restoreState same filter — never reads it back (#401)
windowSession.svelte.ts drops restored tabs without a real file path
mergeSelfInto closes a home tab instead of moving it to the target window

So the method was the one remaining way to produce a state the rest of the system
is built to reject. That is the argument for removing it rather than leaving it
as a harmless unused method.

Also removes Editor.svelte's onhome prop and the onhome={...} the viewer
passed to it. Unrelated to the above, noticed in the same file.

Mechanism

addHomeTab. Before #480, Ctrl+T had two meanings depending on where the
caret was: Monaco's file-new action inside the editor, this branch outside it.
#480 settled both on new-file, and the branch that called addHomeTab went with
it. Nothing else ever called it.

onhome. Declared and typed in e2fae4c and never read. Every sibling prop in
Editor.svelte appears three times — destructured, typed, and called from an
editor.addAction({ run: () => onX?.() }). onhome appears twice:

onsave: 3   ontoggleEdit: 3   onnextTab: 3
onnew:  3   ontoggleLive: 3   onprevTab: 3
...         onhome:       2   onundoClose: 3

The props block was written as a batch; the Home entry in the command table never
followed. So MarkdownViewer was wiring onhome={() => (showHome = true)} to a
callback nothing could fire. The home screen itself is untouched — the titlebar
button and the showHome toggle are its live routes, and the editor has no Home
command to lose.

Scope

The HOME sentinel and isHomePath stay, deliberately. Only the constructor
is dead; the recognition is not. Snapshots written before #401 have HOME in
them and are sitting on users' disks right now. What turns those away is
hasRealFilePath, and that predicate is spelled in terms of isHomePath — the
recognition IS the rejection. Delete it and #401's phantom tab comes straight
back. The render gate from #429 and the tab-strip guards keep reading it for the
same reason: they are what a stray home tab would run into. homeTab.ts now says
this where the sentinel is defined, so the next person to find an "unused"
sentinel finds the reason too.

Left alone on purpose:

  • The tabs.home translation key, in 26 locale files. addHomeTab was its
    only consumer in src/. Removing it is a large diff across every locale for no
    behavioural gain, homeTabRender.test.ts still uses it to prove the gate does
    not depend on the title, and it is what a reinstated home tab would need. Happy
    to do it in a follow-up if you'd rather it went.
  • formatShortcutKeymap.test.ts's no path anywhere still opens a Home tab
    test
    , which asserts no chord reaches addHomeTab. It is feat(editor): shortcuts for the six unbound formatting commands, and one meaning for Ctrl+T #480's fence and now
    passes trivially. Rewriting another PR's test from this one seemed worse than
    leaving it.
  • No version bump — the recent history keeps those in separate chore: bump pkg commits.

Tests

Four suites called addHomeTab to construct a home tab so they could test
recognition. The recognition is what stays, so they stay too and build the
state themselves:

function openHomeTab() {
	tabManager.addNewTab();
	tabManager.activeTab!.path = HOME_TAB_PATH;
}

Setting path is the whole of it — isHomePath is a predicate over path and
nothing else. This is arguably the more honest fixture: it no longer inherits a
title from a constructor that the gate is explicitly required to ignore.

  • homeTabRender.test.ts, homeSentinelSnapshot.test.ts — local openHomeTab.
  • renderedHtmlField.test.tsaddHomeTab dropped from the construction-site
    enumeration, expected tab count 5 → 4.
  • lossyDecodeSaveGuard.test.tsthis one I didn't anticipate. It counts
    hasReplacementChars: false occurrences as a census of construction sites and
    expected 4; removing one made it 3. It went red on the first run and is the
    reason the census now names the three sites it is counting.
  • untitledTitle.test.tssliceBetween(tabs, 'addNewTab()', 'addHomeTab()')
    re-anchored to 'insertTransferredTab(', the method that now follows.
  • tabPathIdentity.test.tsdeleted the home tab is a singleton on its own terms. Its subject was addHomeTab's own dedupe (a second call re-activated
    rather than duplicated); with one call site left there is no second call to
    make. The half that outlives it — that the sentinel is not a file path and so
    is not subject to the claim rule — is hasRealFilePath's job and is already
    covered by the untitled-tab case beside it. A comment records what left.

Does the fence still hold? Per the template: I dropped isHomePath from the
render gate ({#if tabManager.activeTab && !showHome}) and re-ran
homeTabRender.test.ts — 4 of 8 red, every one of the home-tab cases:

✖ the home tab renders the home screen, not an empty document
✖ a home tab opened next to files still renders the home screen
✖ the home screen is reached by tab kind, never by the tab title
✖ switching from the home tab back to a file returns to the document

The three neighbour cases stayed green, so the tests still distinguish the branch
rather than always choosing HomePage. Gate restored, suite green.

I ran the same kind of check on the onhome removal, since a dead prop is
exactly the sort of thing no test covers: re-adding onhome={...} to the viewer
with the prop gone makes npm run check fail with

src/lib/MarkdownViewer.svelte 3324:9 "Object literal may only specify known
properties, and '"onhome"' does not exist in type '$$ComponentProps'."

so the two sides are held together by the type checker, not by my having noticed.

Verification

All four CI commands, on macOS (darwin 25.5.0), on top of #480:

npm audit        found 0 vulnerabilities
npm run check    649 FILES 0 ERRORS 0 WARNINGS
npm test         737 tests, 737 pass, 0 fail
cargo test       157 passed; 0 failed

Not verified: nothing here was exercised in a running app — this is a
removal, and the argument that it is safe rests on the caller census
(grep addHomeTab src/ is empty, onhome appears zero times outside its own
declaration) plus the type checker, not on clicking around. Windows and Linux
untried; nothing in the diff is platform-conditional. cargo test is included
because CI runs it, but no Rust changed.

`TabManager.addHomeTab` built a tab whose `path` is the `HOME` sentinel.
Its only caller was the document-level Ctrl+T branch in
MarkdownViewer.svelte, and #480 pointed that chord at `handleNewFile`,
which leaves the method reachable from tests alone.

It is not just uncalled, it is the odd one out: everything else in the
tab pipeline already treats a home tab as something to get rid of.
`serializeState` refuses to write the sentinel and `restoreState` refuses
to read it (both filter on `hasRealFilePath`, #401), windowSession drops
restored tabs without a real file path, and `mergeSelfInto` closes a home
tab rather than moving it. A constructor for a state the rest of the
system is built to reject is a way back in, not a feature.

What stays is the recognition. `isHomePath` and the sentinel it reads are
what turn away the `HOME` entries in session snapshots written before
#401, which are on users' disks now; `hasRealFilePath` is spelled in
terms of it, so removing it would readmit the phantom tab that issue was
about. The render gate from #429 and the tab-strip guards keep reading it
for the same reason. homeTab.ts says so where the sentinel is defined.

Also drops Editor.svelte's `onhome` prop and the `onhome={...}` the
viewer passed it. The prop was declared and typed in e2fae4c and never
read: every sibling prop appears three times in that file (destructured,
typed, and called from an `editor.addAction`), `onhome` appeared twice.
The editor's command table has no Home action, so the viewer was wiring
up a callback that nothing could fire. The home screen is unaffected —
the titlebar button and the `showHome` toggle are its live routes.

Tests: the four files that called `addHomeTab` were testing recognition,
not construction, so they build the state themselves and stay. Verified
the fence still holds by dropping `isHomePath` from the gate: all four
home-tab tests in homeTabRender.test.ts go red. tabPathIdentity's
singleton test is deleted — its subject was the method's own dedupe, and
there is no second call left to make.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@PathGao
PathGao changed the base branch from master to feat/format-shortcuts-and-ctrl-t August 6, 2026 02:24
@PathGao
PathGao deleted the branch feat/format-shortcuts-and-ctrl-t August 6, 2026 02:42
@PathGao PathGao closed this Aug 6, 2026
@PathGao

PathGao commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up opened as #486 — removes tabs.home from all 26 locales, as offered in the Scope section above. It is branched on this PR, so the chain is #480#482#486.

PathGao added a commit that referenced this pull request Aug 6, 2026
Follow-up to #482, which removed `TabManager.addHomeTab`. That method's
`title: t('tabs.home', settings.language)` was the key's only consumer in
`src/`, and the tab it titled no longer exists — nothing is left to name.

26 locales, one leaf each, all of them the second entry of a `tabs`
block that keeps `untitled`. `menu.home` and the whole `home.*` section
are untouched: the home SCREEN is very much alive, reached by the
titlebar button and the `showHome` toggle. Only the tab's title is gone.

homeTabRender.test.ts read the key to prove the render gate ignores the
title in every language. It now reads `menu.home`, which every locale
also defines. The claim under test is that the title cannot affect which
branch runs, not which key the title came from.

Nothing type-checks a `t()` key — the `Translation` interface is an index
signature — so both directions were verified against i18nCoverage.test.ts
rather than assumed:

  * leave the key in one locale after removing it from English, and
    `no language defines a key English does not have` fails with
    `tabs.home [1] ja`. A partial sweep across 26 locales cannot pass.
  * point any src file at `t('tabs.home', …)` again, and both
    `every key the source asks for exists in English` and `t() never
    echoes a key back for a reachable string` fail. A missed call site
    cannot pass either.

npm audit / npm run check / npm test / cargo test all clean.

Co-authored-by: PathGao <gaoyanbo@gaoyanbodeMacBook-Air.local>
Co-authored-by: Claude Opus 5 <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.

1 participant