Skip to content

feat(ui): first-run announcement for Ask this session and non-blocking reviews - #1690

Merged
backnotprop merged 16 commits into
mainfrom
feat/announce-ask-session
Oct 4, 2026
Merged

backnotprop merged 16 commits into
mainfrom
feat/announce-ask-session

Conversation

@backnotprop

@backnotprop backnotprop commented Oct 4, 2026 •

Copy link
Copy Markdown
Owner

Merge after #1686. This branch is main + feat/claude-mod-default-on (#1686), so the diff shows #1686's four commits until that one lands. The new work is the commits from 0e1e8de7 on (the review fixes and additions are f50d9273, f6e5cab5, e1f0f75d and e7d56c91).

What

A one-time, video-first announcement for 0.28.0's integrated sessions, built exactly like the terminal-tools announcement (#1529):

  • Ask AI goes to your agent session. When Claude Code, Pi or OpenCode opened Plannotator, Ask AI is answered by that session: the question shows up in the agent's chat and the answer streams back into Plannotator.
  • Reviews no longer hold the agent. Your decision arrives later as a message. (For OpenCode the copy says "code review and annotate", because its plan review still holds the session.)

Files: packages/ui/components/AskSessionAnnouncementDialog.tsx, packages/ui/utils/askSessionAnnouncement.ts (one plain cookie, plannotator-announce-ask-session-seen, for every surface, read through storage directly for the same seeding reason as the precedent). Same shell: portal, z-[100], Escape, Tab wrap, focus restore, backdrop dismiss, and a capture-phase keydown that swallows Mod+Enter so a keystroke can't approve a plan or post a review behind it. data-ask-session-announcement-dialog for tests.

Copy: headline "Ask {Agent}, right from Plannotator" (the connected host), two sentences, and a footer that always reads "This session is connected. Open Ask AI to try it." A Watch on X link to https://x.com/plannotator/status/2106875215170899992, done exactly like the precedent's: X mark and label, outbound style, new tab with noopener noreferrer, and it doesn't dismiss or spend the cookie. If the media can't load, the frame offers this link, like the precedent. A Learn more link to https://docs.plannotator.ai/open-source/workflows/ask-this-session, which goes live with the 0.28.0 release; until then it returns 404 on the live site and 200 on the docs preview. The link opens in a new tab. Like the precedent's outbound links, following it neither dismisses the announcement nor spends its cookie. Got it stays the primary action and keeps focus. Both links come before it in Tab order, and the three actions wrap as one right-aligned group, so a narrow panel never splits them.

The footage: what is real

apps/marketing/public/assets/ask-this-session-demo.{mp4,webm} + ask-this-session-poster.jpg: 1280x800, 35 s, mp4 2.4 MB, webm 2.6 MB, poster 135 KB (the TUI/Herdr demos are 5.5 MB and 3.5 MB mp4). They load from https://plannotator.ai/assets/ like the precedent, after the marketing deploy that runs on push to main.

  • Real: Claude Code 2.1.289 running interactively in a PTY, with this repo's plugin (--plugin-dir apps/hook) and the Claude Code mod in its default-on state. The CLI is the real compiled one from this branch. The review UI is the real built app. /plannotator-review opens the review through the mod. The question is sent from the line-selection popover over the real pull bridge, arrives in Claude Code as "The plannotator plugin sent a message", and Claude Code runs a real Read tool call. The answer streams back into the Ask AI panel ("Ask this session · Claude Code" is visible). Then a comment is sent with Send Feedback, the tab shows "Feedback Sent", and the feedback arrives in Claude Code as a plugin turn.
  • Scripted: only the model. Claude Code's ANTHROPIC_BASE_URL pointed at a local stand-in for the Messages API that streams fixed text (plus the Read tool_use). No real model ran, and no account or credentials were used. Everything ran with a temp HOME and a temp PLANNOTATOR_DATA_DIR.
  • Composition: the stage page is mine: two window frames, the terminal rendered with xterm.js wired to that PTY, and the app in an iframe. So are the pointer overlay (headless Chromium draws none) and the zoom/pan camera, which was applied in post from a 3200x2000 capture so zoomed shots stay sharp.
  • The footage shows Claude Code. For a Pi or OpenCode session the headline names that host, but the video is still Claude Code. Separate Pi and OpenCode clips would be a follow-up.

Who sees it, and why

  • Shown only while this session is actually connected: /api/ai/capabilities lists the session-bridge provider for claude-code, pi or opencode, and its status is not gone (connectedAskSessionAgent). Ask AI must also be reachable on the surface: the plan editor's canUseAI, not taken over by the annotate agent terminal, or code review's AI button.
  • Deferred, never consumed, so the reader sees it in a session where it is true:
    • no bridge (remote, --tailscale, Windows, the mod off, -p, Pi's event-API path, OpenCode 1, an older CLI);
    • a gone session;
    • other hosts, or PLANNOTATOR_AI=disabled;
    • archive, shared/read-only and no-server sessions;
    • the compact touch shell and the initial load.
  • Never mid-work (useFirstRunAnnouncementWindow): it opens only before the reader's first pointer press, key press or focus into a text field, and within 4 s of the initial load. It never opens over a focused text field (such as a comment composer) or a focused iframe. On raw-HTML and live-app annotate, work inside the iframe never reaches the parent's listeners, so a window blur after a one-second startup grace also closes the window; the grace is there because the viewer may focus its own iframe while loading. Capabilities can answer late because of model discovery. If it does, the announcement misses that load and the cookie stays unwritten, rather than taking focus mid-comment and being dismissed unread by the next Space or Enter.
  • Ordering: LAST in each app's chain, behind every chain dialog including Claude Code's permission-mode setup. It also comes after the terminal-tools announcement and never on the same load (askSessionAnnouncementPendingThisLoad), so a fresh browser sees terminal tools first and this one on a later load. Code review's destination spotlight, auto-viewed toast and history-shortcut guard defer behind it, the same way they defer behind terminal tools.

Screenshots

Final footer (Watch on X, Learn more, Got it), from the real built review app with a connected Claude Code session:

Dark, 1440x900 Dark, 820x700

The screenshots below are from earlier revisions of the footer. Everything above the footer is unchanged.

Real built apps (bun run --cwd apps/review build && bun run build:hook, then the compiled CLI) in headless Chromium, fresh profile, DPR 2. Each session was started with PLANNOTATOR_ORIGIN and the real pull-bridge env (all screenshots are connected sessions; the not-connected variant no longer exists). The video was not deployed yet, so the hosted URLs were served from the committed files. The images were committed in a temporary commit and removed in the next one.

Plan review, Claude Code, connected (dark) Plan review (light)
Annotate, Pi, connected (dark) Annotate (light)
Code review, OpenCode, connected (dark) Code review (light)
With the Learn more link (dark) With the Learn more link, narrow 820x700
Short window 1280x640: the panel narrows so video and footer fit Narrow 820x700

Real-app checks (compiled CLI, fresh profiles)

  • A connected session with nobody touching the page: the dialog opens; that run produced every screenshot above.
  • A connected session where the page is clicked right after load: no dialog, cookie unset.
  • No bridge (claude-agent-sdk only): no dialog, cookie unset.
  • A bridge whose session went gone (no host poll within 30 s): no dialog, cookie unset.

Tests

  • packages/ui/utils/askSessionAnnouncement.test.ts: the cookie is read without writing; a different version doesn't count; it is never pending on the same load as terminal tools. Connected means a listed bridge that is not gone: ready, busy and blocked count; gone, missing, an unknown host and a provider row with no status don't. Each eligibility condition works on its own.
  • packages/ui/hooks/useFirstRunAnnouncementWindow.test.tsx (DOM):
    • it opens when the conditions hold before any interaction;
    • a first pointer press or key press defers it, as does focus into a textarea (focus on a button doesn't);
    • it never opens over a focused field, and late conditions miss the window;
    • the clock starts only after load, and once open it stays until dismissed;
    • it never opens with an iframe focused, a window blur after the startup grace defers it, and a blur during startup does not.
  • packages/ui/components/AskSessionAnnouncementDialog.test.tsx (DOM): labelled modal with one completion action that gets focus; names each host; the footer is always the connected line; Watch on X and Learn more open in a new tab without dismissing, while Got it keeps focus and comes after both in Tab order; the media fallback offers the X post; hosted, muted, looping, autoplaying media; reduced motion; load-failure fallback; Got it, Escape and backdrop dismiss (a press inside the panel doesn't); Mod+Enter swallowed; Tab wrap and focus restore.
  • packages/editor/App.askSessionAnnouncement.test.tsx (DOM, real App):
    • shows once for a connected session, then the cookie retires it, and it names the connected host;
    • not connected (no bridge, gone, codex) and AI off: no dialog, cookie unset;
    • a click before a late capabilities answer: no dialog, cookie unset;
    • it never shares a load with terminal tools and shows on the next load;
    • it defers behind look-and-feel and behind permission-mode setup;
    • archive and compact suppress it;
    • Mod+Enter over it decides nothing.
  • packages/review-editor/App.decisionControl.test.tsx: it takes the last turn, Mod+Enter posts nothing, and dismissing it writes the cookie. Separately, it never shares a load with terminal tools.
  • All three new DOM files are added to DOM_TESTS in .github/workflows/test.yml.

Results after the fixes: bun run typecheck clean. bun test packages/ui packages/editor packages/review-editor: 1725 pass, 0 fail. DOM_TESTS=1 bun test packages/editor/App packages/review-editor/App packages/ui/utils plus the three new DOM files and the terminal-tools dialog test: 1154 pass, 0 fail. bun test scripts/dom-test-allowlist.test.ts passes. bun run --cwd apps/review build && bun run build:hook succeeds. Before the fixes, the full bun test run was 6000 pass, 0 fail.

Docs: an AGENTS.md section next to the terminal-tools one, and a manual checklist in tests/UI-TESTING.md.

backnotprop added a commit that referenced this pull request Oct 4, 2026
… and never mid-work

Review of #1690:

- Only a session that is connected right now sees it: the capabilities
  answer lists the session-bridge provider and its status is not gone.
  Everything else (remote, --tailscale, Windows, the mod off, -p, Pi's
  event-API path, OpenCode 1, an older CLI, a gone session, other hosts)
  defers without writing the cookie. The setup lines are gone and the footer
  always says the session is connected.
- It opens only before the reader's first pointer press, key press or focus
  into a text field, within 4 s of the initial load, and never over a
  focused text field (useFirstRunAnnouncementWindow). A late capabilities
  answer no longer pops it up mid-comment for the next Space or Enter to
  dismiss unread; it waits for a later load instead.
- It gates on Ask AI being reachable on the surface (the plan editor's
  canUseAI, not taken over by the annotate agent terminal; code review's AI
  button), so 'Open Ask AI to try it' never points at nothing.
…me gate

Announces 0.28.0's integrated sessions: Ask AI answered by the Claude Code,
Pi or OpenCode session that opened Plannotator, and reviews that no longer
hold that session. One cookie, plannotator-announce-ask-session-seen, for
every surface, read through storage directly like the terminal-tools gate.

Same shell as the terminal-tools announcement (portal, z-[100], Escape, Tab
wrap, focus restore, backdrop dismiss, capture-phase keydown that swallows
Mod+Enter). The copy names the host; the footer says the session is
connected when the server offers the bridge and otherwise what gets it.
The gate shows it only for those three origins, only once Ask AI is
available, and never on the same load as a pending terminal-tools
announcement.
…he first-run chain

Both apps gate it behind every chain dialog (permission-mode setup
included) and behind the terminal-tools announcement, which keeps its own
load. Code review's destination spotlight, auto-viewed toast and history
shortcut guard defer behind it too. Archive, shared and no-server sessions,
the compact touch shell, other origins and PLANNOTATOR_AI=disabled suppress
it without consuming the cookie.
1280x800, 35 s: mp4 (2.4 MB), webm (2.6 MB) and a poster jpg, served from
https://plannotator.ai/assets/ once the marketing deploy syncs them.
Recorded from a real Claude Code 2.1.289 session with the real plugin, mod
and compiled CLI and the real review app; the model calls went to a
scripted local stand-in for the Messages API.
… and never mid-work

Review of #1690:

- Only a session that is connected right now sees it: the capabilities
  answer lists the session-bridge provider and its status is not gone.
  Everything else (remote, --tailscale, Windows, the mod off, -p, Pi's
  event-API path, OpenCode 1, an older CLI, a gone session, other hosts)
  defers without writing the cookie. The setup lines are gone and the footer
  always says the session is connected.
- It opens only before the reader's first pointer press, key press or focus
  into a text field, within 4 s of the initial load, and never over a
  focused text field (useFirstRunAnnouncementWindow). A late capabilities
  answer no longer pops it up mid-comment for the next Space or Enter to
  dismiss unread; it waits for a later load instead.
- It gates on Ask AI being reachable on the surface (the plan editor's
  canUseAI, not taken over by the annotate agent terminal; code review's AI
  button), so 'Open Ask AI to try it' never points at nothing.
On raw-HTML and live-app annotate, pointer, key and focus events inside the
iframe never reach the parent document, and the parent sees only the
<iframe> as focused. The announcement window now treats a focused iframe at
open time as working (skip this load), and closes on a window blur, counted
only after a one-second startup grace so a viewer that focuses its own
iframe while loading does not cost the reader the announcement.
A 'Learn more' link in the footer to
https://docs.plannotator.ai/open-source/workflows/ask-this-session (goes live
with the 0.28.0 release), opened in a new tab with noopener noreferrer.
Got it stays the primary action and keeps focus; the link sits before it in
Tab order. Like the terminal-tools announcement's outbound links, following
it neither dismisses the announcement nor spends its cookie.
A 'Watch on X' action to https://x.com/plannotator/status/2106875215170899992,
the same as the terminal-tools announcement's: X mark and label, outbound
style, new tab with noopener noreferrer, and following it neither dismisses
nor spends the cookie. The actions (Watch on X, Learn more, Got it) wrap as
one right-aligned group so a narrow panel never splits them; Got it stays
primary and focused. When the media cannot load, the frame offers the X post
like the precedent. XMark is now exported from the terminal-tools dialog.
@backnotprop
backnotprop force-pushed the feat/announce-ask-session branch from dc8aa97 to ce3181b Compare October 4, 2026 23:20
@backnotprop
backnotprop merged commit 156c178 into main Oct 4, 2026
28 checks passed
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