Skip to content

feat(web): select a dashboard session from a #session=<id> link - #507

Merged
Ark0N merged 1 commit into
Ark0N:masterfrom
irisitymichaelgrundberg:feat/select-session-from-url
Oct 1, 2026
Merged

Ark0N merged 1 commit into
Ark0N:masterfrom
irisitymichaelgrundberg:feat/select-session-from-url

Conversation

@irisitymichaelgrundberg

Copy link
Copy Markdown
Contributor

An outside page that keeps one Codeman window open can now switch that window between sessions without reloading it. It links to the dashboard as /#session=<id>, and the dashboard selects that tab.

This adds a small new integration surface, so I'm opening it as a draft. I'm happy to move the design to an issue first if you'd prefer that.

Why

I run a task board that opens each agent's session in one reused browser window. The only per-session URL today is the solo page at /session/<id>, and every click on it loads the whole app again: the HTML shell, the scripts, the event stream and the terminal buffer. Switching between a few sessions waiting on me took a few seconds each time. The dashboard already switches tabs quickly, and it draws a cached terminal for a session it has shown before, so the only thing missing was a way to tell an open dashboard which tab to show.

What it does

  • The link. sessionIdFromFragment() in constants.js reads session=<id> from the fragment. The dashboard reads it at construction and on every hashchange, then removes the fragment with history.replaceState. A later link to the same session is therefore still a change the browser reports, even after the user has clicked away to another tab.
  • No reload. When a window already shows the dashboard, a new link differs from it only in the fragment. The browser treats that as same-document navigation, so the page stays loaded and the switch is an ordinary selectSession.
  • An id the dashboard doesn't list yet. A page that has just created a session can link to it before session:created reaches this window. The id waits in _urlSessionId until _onSessionCreated names it. handleInit also checks it before restoring the last active tab, so a fresh page load lands on the linked session.
  • Picking another tab retires a waiting link. If you select a different tab yourself, meaning any selection without auto, the waiting id is dropped. Without that, a session that turned up late would take the tab from you.
  • Idle alerts stay armed. A link selects with { auto: true }. The page that set the fragment may be a script, and the window may not be in front, so following a link is not a human looking at the session. The alert clears as it does today, on a click or on input. docs/architecture-invariants.md and the source guard in test/session-select-ack-gate.test.ts list this call among the app-made selections.
  • Solo windows ignore the fragment, and so does the rest of the app, which uses the fragment for nothing else.

docs/extending-codeman.md has a short section on linking to a session from an outside page, under Seam 3.

Testing

  • npm test passes, as do lint, format, check:frontend-syntax and check:public-assets.
  • The new test/url-session-fragment.test.ts covers the parser, and it loads CodemanApp through vm, like the ack-gate test, to cover:
    • removing the fragment;
    • an id waiting for session:created;
    • a user selection retiring a waiting id, and an auto selection keeping it;
    • the link taking priority over the restore;
    • solo windows never reading it.
  • I drove it end to end with Playwright against a beta instance (CODEMAN_INSTANCE=beta), with the board opening sessions in one named window. Switching between three shell sessions took about 90–250 ms with the fragment link and about 500–700 ms with /session/<id>, and a marker set on the window survived every switch. Sessions with long transcripts should gain more.
  • My own Codeman runs this branch now, against my real Claude sessions, and switching from the board behaves as described.

Known limit

A link to a session that another dashboard has popped out goes through the existing detached path. That path posts a focus-request on the window channel, and the pop-out calls window.focus(). Chromium browsers bring the pop-out forward, and I checked that by hand. Firefox may refuse a focus request without a user gesture in that window, in which case the linking window keeps showing its current tab. I haven't tested Firefox.

No changeset, following the repository's convention for contributors.

🤖 Generated with Claude Code

A page that keeps one Codeman window open, such as a task board, could only
show a session by sending that window to /session/<id>, which loads the whole
app again for every click. The dashboard now reads a #session=<id> fragment
when it loads and on hashchange, selects that session, and removes the
fragment with history.replaceState so the next identical link is still a
change. Re-pointing a window that already shows the dashboard changes only the
fragment, so the page stays loaded and the switch is a tab change.

A link can name a session the dashboard does not list yet, because the page
that created it may link before session:created arrives. The id waits until
that event names it, and picking another tab yourself retires it.

Following a link is an app selection (`auto: true`). The page that set the
fragment may be a script, so it must not spend the session's idle alert.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@irisitymichaelgrundberg
irisitymichaelgrundberg marked this pull request as ready for review September 29, 2026 16:09
@Ark0N
Ark0N merged commit 0aba9f6 into Ark0N:master Oct 1, 2026
2 checks passed
Ark0N pushed a commit that referenced this pull request Oct 1, 2026
…eb tab (#507 review)

- A #session=<id> link whose session never appears (closed, a typo, or
  another user's session in multi-user mode) is dropped after
  URL_SESSION_WAIT_MS (30 s) with a "Session not found" toast instead of
  waiting forever. One stored timer per link, cleared whenever the link is
  followed, replaced by a newer link, or retired.
- goHome() and opening a web tab now retire a waiting link, so a session
  that turns up later no longer takes the screen. App-made web tab opens
  (frame self-recovery, the fallback after the active web tab closes) pass
  auto: true and keep it, as selectSession() does.
- zh-CN translation for the new toast.
- selectSession's auto: true comment now lists the #session=<id> link.
- docs: the 30 s bound, a win.location.replace() tip that avoids piling up
  history entries, and the fragment declared a stable SemVer surface in
  versioning-policy.md.
- Tests: timeout drops and toasts, an early arrival is still selected, the
  wait does not restart, goHome and a web tab retire it, an auto web tab
  open keeps it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ark0N pushed a commit that referenced this pull request Oct 1, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Ark0N

Ark0N commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Merged, thanks @irisitymichaelgrundberg! This ships in 1.33.3. The design reads well: one fragment, auto: true so following a link never spends an idle alert, solo windows left out, and the id only ever looked up in the session list this client already has. I also checked it end to end against an isolated server, and every behaviour in the description held.

I folded the review items into one commit at merge time (846c62f), so here's what changed on top of yours:

  • A link can't wait forever. A pending id now waits at most 30 seconds (URL_SESSION_WAIT_MS), then shows a "Session not found" toast. That covers a stale board link, a typo, or another user's session in multi-user mode, which used to do nothing visible. Going Home or opening a web tab now drops a waiting link too, the same as picking another tab, so a late session can't take the screen. Web tabs the app opens by itself pass auto: true and leave the link alone, like selectSession.
  • Docs. docs/extending-codeman.md suggests win.location.replace(url) for later links when the page holds the window reference. A plain navigation adds a history entry on every link, so Back in the dashboard turns into a no-op. docs/versioning-policy.md now lists #session=<id> as a stable surface, so your board can rely on it across minor releases.
  • The auto: true comment in selectSession lists the link alongside the other app-made selections.

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.

2 participants