Skip to content

Align the macOS File menu with the in-app project switcher. The File…#373

Closed
inkeep-oss-sync[bot] wants to merge 6 commits into
mainfrom
copybara/sync
Closed

Align the macOS File menu with the in-app project switcher. The File…#373
inkeep-oss-sync[bot] wants to merge 6 commits into
mainfrom
copybara/sync

Conversation

@inkeep-oss-sync

Copy link
Copy Markdown
Contributor

Align the macOS File menu with the in-app project switcher. The File menu's project actions now sit together directly under "New from template…" and read in the same order as the bottom-left switcher: Recent project, New project, Switch project, Open folder. "Create new project…" is renamed "New project…" and the recents submenu is renamed "Recent project" to match the switcher's labels. The switcher's own action order is updated to the same sequence (New project, Switch project, Open folder) so both surfaces are consistent. Wiring, accelerators, and behavior are unchanged.

Fix links({ kind: "dead" }) falsely reporting freshly-written docs as dead. The dead-link check decided a target existed only from the file-watcher's file index, which lags behind in-session writes, so a doc the link graph had just registered a backlink for could still be flagged dead until a server restart. Dead-link resolution now also treats any doc the graph already holds as a live node (its body has been indexed) as a valid target, so a newly-written doc is a valid link target immediately — without changing how genuinely-missing targets are reported.

mike-inkeep and others added 6 commits June 29, 2026 17:05
…ift toast (#2242)

* fix(desktop): shut down stale server on app update to stop version-drift toast

The desktop spawns its CRDT server detached so it survives app-quit
(precedent #1261). But app updates install via app-quit too — either the
silent `autoInstallOnAppQuit` path or a drag-replace — and only the
"Relaunch now" button ran `prepareForRelaunch` → `stopAllOwnedServers`.
So on the common update paths the old-version server kept running, the
relaunched app attached to it, read the older version off `server.lock`,
and showed the "this project is running an older version… restart it"
toast on nearly every update.

Hook the teardown to `before-quit-for-update` — the native autoUpdater
signal that fires on BOTH install paths and ONLY on an update install,
never a plain quit. New `WindowManager.signalStopAllOwnedServers()` sends
a synchronous best-effort SIGTERM to every detached server this desktop
spawned (the "Relaunch now" path already drained them, so it no-ops
there). The event can't hold the quit open, but the server's own SIGTERM
handler flushes pending writes and releases its lock in ~25ms (measured) —
far inside the multi-second reinstall+relaunch window — so the new app
spawns fresh instead of re-attaching. A normal quit never fires the event,
so it still leaves the server running, as designed.

The signalling loop is extracted as the pure `signalDetachedServerStop`
helper with unit coverage (normal kill, ESRCH already-gone, non-ESRCH
failure keeps going, empty no-op), since `WindowManager`'s teardown
methods have no unit harness.

Claude-Session: https://claude.ai/code/session_01Se7d83STuBxsEbdmU4pWD2

* fix(desktop): also reap ephemeral servers on update + dedup utility-fork loop

Address both reviewer "Consider" findings on #2242:

1. Ephemeral single-file (`ok <file>`) session servers are tracked on
   `ctx.ephemeral` (keyed by file path, not project root) and so weren't
   reaped by the new before-quit-for-update teardown — they'd orphan on the
   silent `autoInstallOnAppQuit` path (process + temp dir until reboot), the
   way the async sibling `stopAllOwnedServers` already prevents. Signal their
   pids too. Temp-dir removal is the async half this best-effort path can't
   do; the orphaned process (which holds the bundle binary) is what matters
   and dies on the SIGTERM.

2. The utility-fork SIGKILL loop was duplicated near-verbatim across both
   teardown methods. Extract the shared pure `signalStopOwnedUtilityForks`
   helper and call it from both, so the predicate + error handling can't
   drift — and the branch gains unit coverage (3 new tests).

Claude-Session: https://claude.ai/code/session_01Se7d83STuBxsEbdmU4pWD2

* test(desktop): cover signalStopAllOwnedServers orchestrator end-to-end

Re-review follow-up: the extracted helpers were unit-tested but the
orchestrator entry point `signalStopAllOwnedServers()` (wired to
before-quit-for-update) had no direct coverage of its composition —
collecting detached pids from `spawnedDetachedPids` + ephemeral pids from
`windowsByPath`, draining the map, merging. Add a test through the existing
ephemeral harness: seed a detached pid + an open ephemeral session, assert
both receive SIGTERM, and assert a second call doesn't re-signal the
detached pid (map drained). Guards against a future field-rename in the
collection chain that would pass the helper tests but break orchestration.

Claude-Session: https://claude.ai/code/session_01Se7d83STuBxsEbdmU4pWD2

---------

GitOrigin-RevId: ab2b0fe9c2a6d159d9b3f3ccb3183fc170ae952d
…251)

pickLatestBetaDmgUrl returned the first -beta.N release in GitHub's
"List releases" array order, trusting it to be newest-first. It isn't:
the API has been observed returning an older beta ahead of newer ones,
and the bug surfaced the moment betas crossed from single- to
double-digit (beta.9 was served while beta.10-13 existed), stalling
/download/beta and the /updates/beta auto-update feed on a stale build.

Pick the newest beta by numeric (major, minor, patch, beta) rank instead
of position. Fixes both surfaces, since the updates route derives its tag
from the same resolver. Adds regression tests for the older-first array
order and the beta.9-vs-beta.10 lexical trap.

GitOrigin-RevId: 353c018fe8860fd1ad02051f36989f6d2c97715c
GitOrigin-RevId: aa53d68b0739a9b4e289a10c46ab732544579bfa
* feat: align macOS File menu with the in-app project switcher

Group the File menu's project actions (Recent project, New project,
Switch project, Open folder) directly under "New from template..." in
the same order as the bottom-left ProjectSwitcher, and update the
switcher's action order to match. Rename "Create new project..." to
"New project..." and "Open recent" to "Recent project" so both
surfaces read identically. Wiring, accelerators, and behavior unchanged.

* chore: centralize project menu labels, refresh stale references

Address PR review feedback:
- Fold the dual-surface 'New project' / 'Open folder' labels into MENU_LABELS
  so menu-label-parity.test.ts guards menu<->switcher wording drift (matching
  the existing shared-label pattern); normalize their ellipsis to the same
  escape the sibling labels use.
- Add contiguity assertions to the menu order test so a separator inserted
  mid-section would be caught, not just relative ordering.
- Refresh stale 'Create New Project...' references to the renamed label at the
  onNewProject call sites (index.ts, App.tsx, CreateProjectMenuTrigger,
  NavigatorApp) and in the onNewProject JSDoc.

GitOrigin-RevId: ac7db1562d92e3ba8230ff7b17daa106d33d9e2a
…cs (PRD-7194) (#2248)

* fix(open-knowledge): stop dead-link check flagging freshly-written docs

`links({ kind: "dead" })` reported recently-created docs as dead even
though their target files existed and the backlink edge was already
registered. Dead-link resolution decided existence solely from the
caller-supplied admitted set (the file-watcher's file index), which lags
behind in-session writes. The same backlink index, meanwhile, registers
a new doc as a live forward node the instant `onStoreDocument` runs — so
the graph simultaneously held a backlink FOR the doc and reported it as a
dead link, an internal inconsistency that only cleared on a server
re-index/restart.

`getDeadLinks` now treats a target as existing when it is admitted OR is
a live forward node in the graph (`state.forward.has(target)`). A
genuinely-missing target is never a forward node (only `state.backward`
carries it), so this never hides a real dead link; a freshly-written doc
becomes a valid link target immediately within the same session.

Fixes PRD-7194.

Claude-Session: https://claude.ai/code/session_01HgfN67bZN3AUMkA2sa7dMf

* test(open-knowledge): pin dead-link deletion inverse + document existence contract

Address claude[bot] review on #2248 (both low-risk suggestions):
- Add a companion test asserting that after `deleteDocument` drops a doc
  from `state.forward`, `getDeadLinks` reports it dead again when it's also
  absent from the admitted set — guards against a future refactor that
  retains stale forward entries and silently swallows real dead links.
- Add JSDoc to `getDeadLinks` making the existence contract explicit:
  existence is `admittedDocs ∪ keys(state.forward)`, an additive second
  oracle, not a narrowing of the admitted set.

Claude-Session: https://claude.ai/code/session_01HgfN67bZN3AUMkA2sa7dMf

---------

GitOrigin-RevId: 6834ae8bfb71348f858c05c55abc489b883733fa
…chain (#2254)

* test(open-knowledge): folder-rename regression coverage for PRD-7045 chain

PRD-7045 (folder rename corruption chain: duplicate folder, stale relative
links, blank WYSIWYG, perceived data loss) is already fixed in code — each
facet by a separate PR that merged before the 6/12 UX session that reported
it:

  - duplicate folder + spontaneous reappearance → #740 (removalRedirectGuard
    + recentlyRemovedDocs, phantom-resurrection defense)
  - blank WYSIWYG while source shows content → #1011 / PRD-6673 (spine moves
    the file on disk before captureAndCloseDocuments, so the redirected client
    loads real content instead of an empty Y.Doc)
  - stale relative ../ links / orphaned inbound links → #1473 / PRD-6839
    (folder rename enumerates descendant docs from disk, not the lagging index)

But the regression suites only covered FILE rename, while PRD-7045's reported
symptom is specifically a FOLDER rename. This adds folder-level coverage so the
chain can't silently regress:

  removal-phantom-resurrection.test.ts (QA-FOLDER): a folder rename arms the
  removal cache for every descendant doc — a reconnect to ANY old descendant
  docName is redirected (resurrection vector severed for each), and every doc's
  body survives the move (the blank-WYSIWYG facet at the disk layer).

  api-folder-rename-disk-enum.test.ts: inbound links in every shape — wiki,
  root-relative, dot-relative, and ../ / ../../ from nested sources — are
  rewritten to the new folder with zero stale references; and relative links
  authored INSIDE the folder (sibling ../, descendant ./sub/, outbound ../)
  still resolve to a real file after the move (no orphans).

Test-only; no behavior change.

Claude-Session: https://claude.ai/code/session_01EVpxrxSVppwkKppyqH396a

* test(open-knowledge): tighten folder-rename link assertions (review)

Address claude[bot] review on #2254:
- Pin full link shapes (`[banana](sub/banana.md)` etc.) instead of loose
  `toContain('sub/banana.md')` substrings, so an incorrect re-relativization
  (e.g. `../recipes/sub/banana.md`) would now fail. Note the rewrite
  normalizes a leading `./` away while resolution stays correct.
- Assert the old `foods/` directory is gone after the rename, matching the
  pre-existing describe block's self-containedness.

Claude-Session: https://claude.ai/code/session_01EVpxrxSVppwkKppyqH396a

* test(open-knowledge): harden QA-FOLDER seed against CI watcher lag

QA-FOLDER failed twice on the merge tier with `pollUntilAsync timed out
after 8000ms` during seeding — the chokidar watcher lagged past 8s before
`/api/documents` reflected the fresh nested file, under a shard also running
the OK_TEST_STORE_FAULT disk-failure-simulation suites. The folder rename
enumerates descendants from disk and the guard keys off the spine-populated
cache + existsSync, so the index-appearance wait is the only fragile part.

Make `seedDoc`'s index-wait timeout overridable (default unchanged at 8000 so
sibling tests keep their timing), pass 20000 for QA-FOLDER's two seeds, and
raise that test's deadline to 60s to fit two generous seeds + the rename.

Claude-Session: https://claude.ai/code/session_01EVpxrxSVppwkKppyqH396a

* test(open-knowledge): seed QA-FOLDER via agent-write-md (deterministic)

The prior fix (bigger seedDoc timeout) still flaked: QA-FOLDER hit the 20s
index-wait in the full test:integration job (990 tests / 220 files), where
the chokidar watcher lags past any fixed timeout under event-loop contention.

Replace the watcher-poll seed with `POST /api/agent-write-md`
(`position: 'replace'`), the same deterministic seed the Playwright tests use:
it lands the doc on disk AND in the index synchronously within the awaited
request, and routes through `writeTracker` so the watcher fires no later `add`
event for the path. The latter also closes a reverse-race — a post-rename
`add` for an old descendant path would otherwise invalidate the spine's
removal-cache entry (`onUpstreamAdd` → `recentlyRemovedDocs.delete`) and flake
the redirect assertion. Bonus: the seed loads a live Y.Doc, so the
body-preservation check now exercises the same "editor connected at rename"
path PRD-6673 fixed. seedDoc reverts to its original signature.

Claude-Session: https://claude.ai/code/session_01EVpxrxSVppwkKppyqH396a

---------

GitOrigin-RevId: 5366010ca559145626a177bc7b2f9acca5e6593d

@inkeep-internal-ci inkeep-internal-ci Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval from agents-private public-mirror-sync (run: https://github.com/inkeep/agents-private/actions/runs/28389188784). Source of truth is the monorepo; direct edits on inkeep/open-knowledge are overwritten on next sync.

@inkeep-oss-sync

Copy link
Copy Markdown
Contributor Author

Auto-closed by public-mirror-sync: a newer sync run is rebuilding copybara/sync with accumulated changes from agents-private. A fresh PR will be opened momentarily.

@inkeep-oss-sync inkeep-oss-sync Bot closed this Jun 29, 2026
@inkeep-oss-sync
inkeep-oss-sync Bot deleted the copybara/sync branch June 29, 2026 18:37
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.

3 participants