| Files | Last commit | Last update |
|---|---|---|
fix: stop the Changes review snap-back and the Pierre reveal give-up behind three e2e flakes (#1128) * fix(web): stop Virtuoso's scrollToIndex retries from undoing a review scroll Flaky e2e: changes-review-drafts "Changes retains unsaved draft-thread edits through virtualization and honors save or Escape" timed out clicking changes-review-prev (run 41203747599): the button re-rendered disabled, the walk back on file 1 after Next. Cause: react-virtuoso re-issues a scrollToIndex whenever item sizes change within 150 ms of its previous attempt, and it never stops for user input. While stacked sections still load and resize, the earlier draft-00 reveal kept being re-applied after the wheel (and after a composer's focus scroll): the list snapped back to draft-00, so the test's "draft-00 is back" assertion passed before the row click's preview-delayed reveal, which then landed after Next and reset activePath to draft-00. That breaks the documented contract that user input takes the reveal over. The list's scroller now applies scrollTo only while a reveal is settling. Evidence: with lazy chunks delayed up to 1.5 s, ~37% of 120 runs showed Virtuoso-internal scrolls (no app frames on the stack) after user input, and one run in 60 lost its wheel outright; with the gate, 0 of 120. * fix(resource-renderers): retry a Pierre line reveal until its rows are laid out Flaky e2e: changes-review "the guide reveals a finding deep inside a bounded (virtualized) section" failed toBeInViewport with ratio 0 (run 41138747897, branch run-project-button; that branch's run.spec failures are unrelated - it touched no review or Pierre code): the finding card stayed in an unassigned annotation slot. Cause: when the guide asks for the finding before Pierre's worker has answered, the section has no rows yet, so Pierre's Virtualizer.scrollTo clamps the reveal to 0 - yet revealLine reported success and usePierreFocus latched it, never revealing line 1500 once the rows landed. revealLine now reports success only when the line lies in the viewport, so the focus reveal retries when the content grows. The test now holds the Pierre worker script (holdPierreWorkers) until the finding is requested, forcing that order: 5/5 failures before the fix, 5/5 green after. * test(e2e): read pi settings only after the switch settles Flaky e2e: pi-packages "a pi package installed from Settings reaches a chat, switches off and on, and is removed" threw "Unexpected non-whitespace character after JSON at position 157 (line 8 column 4)" from readSettings inside expect.poll (run 41224170834). Cause: the poll read pi's settings.json while the host was rewriting it for the tool-search switch. readFileSync's first read returned the old 155-byte file and the next read, after the write grew it, the new file's tail - old JSON followed by ` "extensions": [...`, exactly position 157 - and expect.poll rethrows a callback error instead of polling again. Both switch probes now wait for the switch's data-active, which follows the host's reply (sent after the write) and the catalog re-list, then read the file once. A hot reader racing the toggle hit the identical torn content in 3/20 runs; reading after the switch settled saw 0 torn reads in 20 runs. * test(e2e): poll pi settings through torn reads in the extra-usage spec The extra-usage spec polled JSON.parse of pi's settings.json while the host rewrites it, the same torn read that flaked pi-packages; no UI state marks that write, so the poll now treats a SyntaxError as "not yet". The torn-read and Pierre worker-hold rules move to their own paragraph and to the fixtures paragraph of e2e/SPEC.md. | 6 days ago | |
chore(e2e): move Playwright configs into e2e/ (#1154) * chore(e2e): move Playwright configs into e2e/ Pass -c from the runners and test:workflows, and keep test-results and playwright-report at the repo root so CI artifact paths do not change. * ci: select artifact jobs for the moved binary and desktop configs | 5 days ago | |
chore(e2e): move Playwright configs into e2e/ (#1154) * chore(e2e): move Playwright configs into e2e/ Pass -c from the runners and test:workflows, and keep test-results and playwright-report at the repo root so CI artifact paths do not change. * ci: select artifact jobs for the moved binary and desktop configs | 5 days ago | |
Settings redesign (#710) * refactor(web): redesign the Settings dialog * fix(web): restore Settings sections and render the nav once * fix(web): make collapsible model-picker groups keyboard-safe and keep hidden models out of the store * fix(web): restore subagent limits, streaming movement and file line width in the redesigned Settings * fix(web): restore Layout reset to default and validate group limits * fix(web): restore provider API-key, context, quota, GitHub and notification controls * chore(web): drop the unused ghost-success button variant and refresh the compiler census * docs(web): amend Settings, picker and store specs to the repaired redesign * test(e2e): follow the redesigned Settings and collapsible model-picker groups * fix(web): list Extensions under System in Settings * fix(web): leave System theme mode with the pair's theme for that appearance --------- Co-authored-by: Julia.Shilova <Julia.Shilova@MacBook-Pro-5.local> Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 23 days ago | |
fix(e2e): restore isolated agent test coverage (#512) * fix(e2e): restore isolated agent test runs * test(e2e): stabilize live reading-band coverage * fix(e2e): target the active host during fixture reset | 1 month ago | |
Settings redesign (#710) * refactor(web): redesign the Settings dialog * fix(web): restore Settings sections and render the nav once * fix(web): make collapsible model-picker groups keyboard-safe and keep hidden models out of the store * fix(web): restore subagent limits, streaming movement and file line width in the redesigned Settings * fix(web): restore Layout reset to default and validate group limits * fix(web): restore provider API-key, context, quota, GitHub and notification controls * chore(web): drop the unused ghost-success button variant and refresh the compiler census * docs(web): amend Settings, picker and store specs to the repaired redesign * test(e2e): follow the redesigned Settings and collapsible model-picker groups * fix(web): list Extensions under System in Settings * fix(web): leave System theme mode with the pair's theme for that appearance --------- Co-authored-by: Julia.Shilova <Julia.Shilova@MacBook-Pro-5.local> Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 23 days ago | |
feat(ask-user-question): redesign the questionnaire card — stable height, notes and Other in place, rich previews, Discuss, receipt (#1192) * fix(ask-user-question): make every questionnaire control reachable by keyboard in WebKit * feat(pi-ask-user-question): add question previews, opt-in file previews, discuss-first answers and one-option confirmations * feat(extensions): add per-extension settings and share the workspace file check through the session seam * refactor(resource-renderers): share the framed file preview and add compact chrome for embedded frames * feat(ask-user-question): drive file previews from the rich-preview setting and teach the Discuss and preview conventions * refactor(ask-user-question): stack question pages at a stable height with in-place note slots and a real Other field * feat(ask-user-question): add question previews, a fixed preview pane and a per-question Discuss mark to the card * feat(ask-user-question): replace the answered record with a foldable question → answer receipt * test(ask-user-question): cover stable paging, growing fields, previews, Discuss, the receipt and the rich-preview switch in Chromium and WebKit * docs(specs): note the plan page's preview scope and the WKWebView tab-stop gap * fix(ask-user-question): keep Discuss across reconnects, let the Other checkbox cancel it, and make previews keyboard-scrollable * test(ask-user-question): click Submit only once the page-change reveal has settled * feat(ask-user-question): Enter picks, Space adds a note, d discusses and the platform chord submits in the card * feat(pi-ask-user-question): match the card's keys in the terminal — Enter picks, Space notes, Next and Submit rows, Ctrl+Enter sends * docs(ask-user-question): describe the explicit-selection keyboard model in the specs and live spec * chore(locales): restore package.json and tsconfig.json that a catalog edit rewrote * docs(web): note that the platform modifier now comes from @thinkrail/ui/keyboard * Document extension settings in the contracts, settings, host, store, transport and chat specs, move them to protocol v104 after the GitHub star ask took v103, and narrow stored setting flags with a type guard instead of a cast. * Gate Discuss on a shared ASK_DISCUSS_PROTOCOL_VERSION (v104) so a v103 host, whose formatter would report an empty discuss as no answer, never shows the toggle. * ci(e2e): fail over a stalled apt mirror when installing WebKit's system libraries * fix(server): load extensions with the setting snapshot the loader records, so a change and revert mid-reload never leaves a stale tool schema current * ci(e2e): cap each WebKit dependency install attempt so a trickling apt mirror retries instead of exhausting the wait * ci(e2e): install WebKit's missing libraries without Playwright's font group, whose mirror download stalled past the wait * Move extension settings and Discuss to protocol v105 after background workspace creation took v104 * test(e2e): assert the plan-page answer by its lasting receipt, not the transient sent state the card can leave before WebKit sees it --------- Co-authored-by: OLavrik <Olya_lavrik@inbox.ru> Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 1 day ago | |
chore(e2e): type-check the e2e tree in typecheck:root (#1064) Add e2e/tsconfig.json (root config with an e2e include) and run it as an extra tsc step of typecheck:root. Fix the stale and trivial errors it surfaced: the removed "endTurn" stop reason, untyped evaluate element parameters, unchecked index reads, ask/visualize tool arguments that must be JSON objects, and exact-optional shard options. reload-navigation.spec.ts stays excluded as the remaining to-do. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 7 days ago | |
chore(e2e): move Playwright configs into e2e/ (#1154) * chore(e2e): move Playwright configs into e2e/ Pass -c from the runners and test:workflows, and keep test-results and playwright-report at the repo root so CI artifact paths do not change. * ci: select artifact jobs for the moved binary and desktop configs | 5 days ago | |
feat(server): let the agent name chats and workspaces via set_title (#583) * feat(server): let the agent name chats and workspaces via set_title * test(e2e): check hydrate-midstream for duplicated text, not message count * fix(server): reject a set_title branch without a workspace name | 1 month ago | |
chore: near-zero code comments — rationale lives in specs (#269) Strip ~10.6k comments across apps/packages/e2e/scripts (parser-accurate, string/JSX-safe). What survives: biome-ignore directives, five one-line hazard notes pointing at their specs, and opaque-literal label tables (unicode controls, git ref syntax, DEC modes, chunk-error strings, line-number fixtures). Rationale not yet specced was promoted first: xterm #6065 IME rescue -> panels/SPEC.md; palette-reset ordering + alias self-reference -> apps/web/scripts/SPEC.md. Everything else checked was already recorded in the owning SPEC.md; the abandoned wsProtocol v4-v17 changelog is deleted (git history retains it). Codify the rule where agents read it: AGENTS.md ('Comments: avoid them. Near-zero is the norm.' + a 'No comment creep' handoff audit), writing-specs (specs are the only home for rationale), brainstorming step 9 (self-review checks for rationale comments). | 2 months ago | |
chore(e2e): move Playwright configs into e2e/ (#1154) * chore(e2e): move Playwright configs into e2e/ Pass -c from the runners and test:workflows, and keep test-results and playwright-report at the repo root so CI artifact paths do not change. * ci: select artifact jobs for the moved binary and desktop configs | 5 days ago | |
chore(deps): upgrade pi 0.99.1 → 1.0.0 (#606) * chore(deps): upgrade pi 0.99.1 → 1.0.0 * fix(contracts): type pi event messages with an explicit wire union * test(e2e): deny pi-ai 1.0's Anthropic federation credentials in agent runs * docs(specs): re-pin pi facts against 1.0.0 * docs(spec-graph): wrap the tools SPEC allowed-deps line | 1 month ago | |
feat: in-app survey (#997) * feat(web): replace the interview prompt with a PostHog survey offered from a notification * fix(web): show that a --no-analytics run cannot save survey responses Settings and the survey popup explain the flag instead of asking the questions, and the host refuses the submission so the survey is not marked finished. * fix(web): close the survey dialog when dismissal telemetry is rejected A finished survey or analytics turned off rejects the dismissed event. Postpone and close anyway, and keep the dialog open only when postpone itself fails. * docs(server): record survey answers as the one rich additional payload captureSurveyEvent accepts only survey shown, sent, and dismissed, and the analytics spec names the properties those events may carry. * feat(web): privacy policy link in settings and survey Privacy settings, the survey's request to enable analytics, and the row beside Continue open the readme section that explains what analytics collects. * fix(server): send survey answers once on Send through the basic analytics client The submission names the survey id and iteration it answers, the host refuses a changed, unavailable or invalid one with a coded error, and completion is recorded only after the capture is accepted. The survey endpoint comes from analytics, so CI/test muting and THINKRAIL_POSTHOG_HOST apply, and the dev-run key is gone. * feat(web): offer the survey in one window from the invitation dialog or Settings The interview invitation dialog becomes the survey invitation (Never show again / Not now / Take the survey) and keeps waiting behind onboarding and the consent dialog. Both entry points open the same survey window: the notice on the first page, one question per step, answers sent only on Send, a restart when the survey changed, and a thank-you. Settings > Feedback keeps Discord and info@thinkrail.ai and offers Take the survey when one is launched. The toast extensions, the privacy-policy link and the enable-analytics step are removed. * docs: describe the survey sent on Send and its invitation in specs and README * test(e2e): cover the survey invitation, the shared survey window and its refusals * fix: confirm the survey against a fresh PostHog read on Send and keep the survey window within the viewport Submission no longer trusts the 15-minute display cache: confirmInAppSurvey re-reads the list and an unreadable list refuses with SURVEY_NOT_SENT. A failed refresh keeps the last good copy only while it is younger than an hour. The survey window caps its height and scrolls its body, and a successful Send drops the store's survey so an in-flight load cannot restore it. * fix: expire stale surveys on read, count an accepted answer as sent, and make the submission id HTTP-safe The cached survey is gone an hour after its last successful read and an invitation waits for a due refresh, so an idle host cannot invite from an expired copy. Completion is recorded in memory before the write, and a failed write is logged instead of asking the user to send again. The submission id comes from getRandomValues, which plain-HTTP remote clients still have. * fix(web): put the rating scale's end labels under the scale * test(artifacts): answer the in-app survey list from the loopback PostHog collector * feat(locales): translate the survey, its notice and the invitation into Spanish, Japanese and Chinese * feat(server): opt a source dev host into the PostHog project key THINKRAIL_DEV_POSTHOG=1 passes the local project key so the live survey can be fetched and submitted. A normal dev run stays unkeyed. * fix: improve survey intro line * feat(survey): auto-focus in the custom 'other' answer field * fix: treat PostHog's last choice as the Other option and ignore invitations from pre-v97 hosts With hasOpenChoice, PostHog stores the open choice as the last entry of choices whatever its label, so it is always removed from the list instead of only when it is literally "Other". An older host still sends the retired interview invitation on feedback.interview; the client now opens the survey invitation only from a v97+ host. --------- Co-authored-by: artyom <artyomxx@users.noreply.github.com> Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 8 days ago | |
fix: stop the Changes review snap-back and the Pierre reveal give-up behind three e2e flakes (#1128) * fix(web): stop Virtuoso's scrollToIndex retries from undoing a review scroll Flaky e2e: changes-review-drafts "Changes retains unsaved draft-thread edits through virtualization and honors save or Escape" timed out clicking changes-review-prev (run 41203747599): the button re-rendered disabled, the walk back on file 1 after Next. Cause: react-virtuoso re-issues a scrollToIndex whenever item sizes change within 150 ms of its previous attempt, and it never stops for user input. While stacked sections still load and resize, the earlier draft-00 reveal kept being re-applied after the wheel (and after a composer's focus scroll): the list snapped back to draft-00, so the test's "draft-00 is back" assertion passed before the row click's preview-delayed reveal, which then landed after Next and reset activePath to draft-00. That breaks the documented contract that user input takes the reveal over. The list's scroller now applies scrollTo only while a reveal is settling. Evidence: with lazy chunks delayed up to 1.5 s, ~37% of 120 runs showed Virtuoso-internal scrolls (no app frames on the stack) after user input, and one run in 60 lost its wheel outright; with the gate, 0 of 120. * fix(resource-renderers): retry a Pierre line reveal until its rows are laid out Flaky e2e: changes-review "the guide reveals a finding deep inside a bounded (virtualized) section" failed toBeInViewport with ratio 0 (run 41138747897, branch run-project-button; that branch's run.spec failures are unrelated - it touched no review or Pierre code): the finding card stayed in an unassigned annotation slot. Cause: when the guide asks for the finding before Pierre's worker has answered, the section has no rows yet, so Pierre's Virtualizer.scrollTo clamps the reveal to 0 - yet revealLine reported success and usePierreFocus latched it, never revealing line 1500 once the rows landed. revealLine now reports success only when the line lies in the viewport, so the focus reveal retries when the content grows. The test now holds the Pierre worker script (holdPierreWorkers) until the finding is requested, forcing that order: 5/5 failures before the fix, 5/5 green after. * test(e2e): read pi settings only after the switch settles Flaky e2e: pi-packages "a pi package installed from Settings reaches a chat, switches off and on, and is removed" threw "Unexpected non-whitespace character after JSON at position 157 (line 8 column 4)" from readSettings inside expect.poll (run 41224170834). Cause: the poll read pi's settings.json while the host was rewriting it for the tool-search switch. readFileSync's first read returned the old 155-byte file and the next read, after the write grew it, the new file's tail - old JSON followed by ` "extensions": [...`, exactly position 157 - and expect.poll rethrows a callback error instead of polling again. Both switch probes now wait for the switch's data-active, which follows the host's reply (sent after the write) and the catalog re-list, then read the file once. A hot reader racing the toggle hit the identical torn content in 3/20 runs; reading after the switch settled saw 0 torn reads in 20 runs. * test(e2e): poll pi settings through torn reads in the extra-usage spec The extra-usage spec polled JSON.parse of pi's settings.json while the host rewrites it, the same torn read that flaked pi-packages; no UI state marks that write, so the poll now treats a SyntaxError as "not yet". The torn-read and Pierre worker-hold rules move to their own paragraph and to the fixtures paragraph of e2e/SPEC.md. | 6 days ago | |
chore(e2e): move Playwright configs into e2e/ (#1154) * chore(e2e): move Playwright configs into e2e/ Pass -c from the runners and test:workflows, and keep test-results and playwright-report at the repo root so CI artifact paths do not change. * ci: select artifact jobs for the moved binary and desktop configs | 5 days ago | |
feat(ask-user-question): redesign the questionnaire card — stable height, notes and Other in place, rich previews, Discuss, receipt (#1192) * fix(ask-user-question): make every questionnaire control reachable by keyboard in WebKit * feat(pi-ask-user-question): add question previews, opt-in file previews, discuss-first answers and one-option confirmations * feat(extensions): add per-extension settings and share the workspace file check through the session seam * refactor(resource-renderers): share the framed file preview and add compact chrome for embedded frames * feat(ask-user-question): drive file previews from the rich-preview setting and teach the Discuss and preview conventions * refactor(ask-user-question): stack question pages at a stable height with in-place note slots and a real Other field * feat(ask-user-question): add question previews, a fixed preview pane and a per-question Discuss mark to the card * feat(ask-user-question): replace the answered record with a foldable question → answer receipt * test(ask-user-question): cover stable paging, growing fields, previews, Discuss, the receipt and the rich-preview switch in Chromium and WebKit * docs(specs): note the plan page's preview scope and the WKWebView tab-stop gap * fix(ask-user-question): keep Discuss across reconnects, let the Other checkbox cancel it, and make previews keyboard-scrollable * test(ask-user-question): click Submit only once the page-change reveal has settled * feat(ask-user-question): Enter picks, Space adds a note, d discusses and the platform chord submits in the card * feat(pi-ask-user-question): match the card's keys in the terminal — Enter picks, Space notes, Next and Submit rows, Ctrl+Enter sends * docs(ask-user-question): describe the explicit-selection keyboard model in the specs and live spec * chore(locales): restore package.json and tsconfig.json that a catalog edit rewrote * docs(web): note that the platform modifier now comes from @thinkrail/ui/keyboard * Document extension settings in the contracts, settings, host, store, transport and chat specs, move them to protocol v104 after the GitHub star ask took v103, and narrow stored setting flags with a type guard instead of a cast. * Gate Discuss on a shared ASK_DISCUSS_PROTOCOL_VERSION (v104) so a v103 host, whose formatter would report an empty discuss as no answer, never shows the toggle. * ci(e2e): fail over a stalled apt mirror when installing WebKit's system libraries * fix(server): load extensions with the setting snapshot the loader records, so a change and revert mid-reload never leaves a stale tool schema current * ci(e2e): cap each WebKit dependency install attempt so a trickling apt mirror retries instead of exhausting the wait * ci(e2e): install WebKit's missing libraries without Playwright's font group, whose mirror download stalled past the wait * Move extension settings and Discuss to protocol v105 after background workspace creation took v104 * test(e2e): assert the plan-page answer by its lasting receipt, not the transient sent state the card can leave before WebKit sees it --------- Co-authored-by: OLavrik <Olya_lavrik@inbox.ru> Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 1 day ago | |
feat(web): first-run onboarding — intro, privacy and connect-and-verify wizard, recommendations from detected accounts, and Start a new project (#808) * squash: onboarding-first-run * feat(onboarding): harden account detection, share the consent draft between the dialog and the wizard's privacy step, let custom presets place the TODO List, and align the layout e2e specs with Balanced opening on it * fix(onboarding): advance the privacy step only after its consent save lands, verify a provider connected before the wizard reaches Connect, and record the new-project Default exception in the top-level docs * fix(onboarding): keep provider status in one store snapshot invalidated by every change, adopt a consent choice confirmed elsewhere, fence the new-project setup continuation, offer API keys on every provider, and make the onboarding e2e deterministic * test(projects): write the rollback test's hooksPath with forward slashes so git can parse it on Windows * fix(locales): drop the Spanish strings for the removed login Submit key and the reworded open-project subtitle * fix(locales): drop the Japanese strings for the removed login Submit key and the reworded open-project subtitle * Harden onboarding connect: auto-recheck empty model lists, cancel a pending login on skip, split the wizard into step components, drop the viewport-cap table, and emit a created project once * Verify a connection against an awaited model.refresh when the cached model.list is empty, instead of a fixed-delay re-read * fix(onboarding): re-read provider status on Welcome entry and on another login's success, and hand off only from a current snapshot * test(onboarding): settle the host before each cold start, run every teardown step, and drop wall-clock and inherited git-config dependence * fix(onboarding): take the connect dialog's baseline from its first report, so a sign-in that lands before the opening re-read still hands off * test(onboarding): read config.json tolerantly inside polls, remove the projects folder with retries, and count the toast before it auto-dismisses * test(shared): wait for the Central watcher's second re-arm instead of a fixed 5 ms sleep --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 10 days ago | |
feat(ask-user-question): redesign the questionnaire card — stable height, notes and Other in place, rich previews, Discuss, receipt (#1192) * fix(ask-user-question): make every questionnaire control reachable by keyboard in WebKit * feat(pi-ask-user-question): add question previews, opt-in file previews, discuss-first answers and one-option confirmations * feat(extensions): add per-extension settings and share the workspace file check through the session seam * refactor(resource-renderers): share the framed file preview and add compact chrome for embedded frames * feat(ask-user-question): drive file previews from the rich-preview setting and teach the Discuss and preview conventions * refactor(ask-user-question): stack question pages at a stable height with in-place note slots and a real Other field * feat(ask-user-question): add question previews, a fixed preview pane and a per-question Discuss mark to the card * feat(ask-user-question): replace the answered record with a foldable question → answer receipt * test(ask-user-question): cover stable paging, growing fields, previews, Discuss, the receipt and the rich-preview switch in Chromium and WebKit * docs(specs): note the plan page's preview scope and the WKWebView tab-stop gap * fix(ask-user-question): keep Discuss across reconnects, let the Other checkbox cancel it, and make previews keyboard-scrollable * test(ask-user-question): click Submit only once the page-change reveal has settled * feat(ask-user-question): Enter picks, Space adds a note, d discusses and the platform chord submits in the card * feat(pi-ask-user-question): match the card's keys in the terminal — Enter picks, Space notes, Next and Submit rows, Ctrl+Enter sends * docs(ask-user-question): describe the explicit-selection keyboard model in the specs and live spec * chore(locales): restore package.json and tsconfig.json that a catalog edit rewrote * docs(web): note that the platform modifier now comes from @thinkrail/ui/keyboard * Document extension settings in the contracts, settings, host, store, transport and chat specs, move them to protocol v104 after the GitHub star ask took v103, and narrow stored setting flags with a type guard instead of a cast. * Gate Discuss on a shared ASK_DISCUSS_PROTOCOL_VERSION (v104) so a v103 host, whose formatter would report an empty discuss as no answer, never shows the toggle. * ci(e2e): fail over a stalled apt mirror when installing WebKit's system libraries * fix(server): load extensions with the setting snapshot the loader records, so a change and revert mid-reload never leaves a stale tool schema current * ci(e2e): cap each WebKit dependency install attempt so a trickling apt mirror retries instead of exhausting the wait * ci(e2e): install WebKit's missing libraries without Playwright's font group, whose mirror download stalled past the wait * Move extension settings and Discuss to protocol v105 after background workspace creation took v104 * test(e2e): assert the plan-page answer by its lasting receipt, not the transient sent state the card can leave before WebKit sees it --------- Co-authored-by: OLavrik <Olya_lavrik@inbox.ru> Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 1 day ago | |
feat(ask-user-question): redesign the questionnaire card — stable height, notes and Other in place, rich previews, Discuss, receipt (#1192) * fix(ask-user-question): make every questionnaire control reachable by keyboard in WebKit * feat(pi-ask-user-question): add question previews, opt-in file previews, discuss-first answers and one-option confirmations * feat(extensions): add per-extension settings and share the workspace file check through the session seam * refactor(resource-renderers): share the framed file preview and add compact chrome for embedded frames * feat(ask-user-question): drive file previews from the rich-preview setting and teach the Discuss and preview conventions * refactor(ask-user-question): stack question pages at a stable height with in-place note slots and a real Other field * feat(ask-user-question): add question previews, a fixed preview pane and a per-question Discuss mark to the card * feat(ask-user-question): replace the answered record with a foldable question → answer receipt * test(ask-user-question): cover stable paging, growing fields, previews, Discuss, the receipt and the rich-preview switch in Chromium and WebKit * docs(specs): note the plan page's preview scope and the WKWebView tab-stop gap * fix(ask-user-question): keep Discuss across reconnects, let the Other checkbox cancel it, and make previews keyboard-scrollable * test(ask-user-question): click Submit only once the page-change reveal has settled * feat(ask-user-question): Enter picks, Space adds a note, d discusses and the platform chord submits in the card * feat(pi-ask-user-question): match the card's keys in the terminal — Enter picks, Space notes, Next and Submit rows, Ctrl+Enter sends * docs(ask-user-question): describe the explicit-selection keyboard model in the specs and live spec * chore(locales): restore package.json and tsconfig.json that a catalog edit rewrote * docs(web): note that the platform modifier now comes from @thinkrail/ui/keyboard * Document extension settings in the contracts, settings, host, store, transport and chat specs, move them to protocol v104 after the GitHub star ask took v103, and narrow stored setting flags with a type guard instead of a cast. * Gate Discuss on a shared ASK_DISCUSS_PROTOCOL_VERSION (v104) so a v103 host, whose formatter would report an empty discuss as no answer, never shows the toggle. * ci(e2e): fail over a stalled apt mirror when installing WebKit's system libraries * fix(server): load extensions with the setting snapshot the loader records, so a change and revert mid-reload never leaves a stale tool schema current * ci(e2e): cap each WebKit dependency install attempt so a trickling apt mirror retries instead of exhausting the wait * ci(e2e): install WebKit's missing libraries without Playwright's font group, whose mirror download stalled past the wait * Move extension settings and Discuss to protocol v105 after background workspace creation took v104 * test(e2e): assert the plan-page answer by its lasting receipt, not the transient sent state the card can leave before WebKit sees it --------- Co-authored-by: OLavrik <Olya_lavrik@inbox.ru> Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 1 day ago | |
feat(web): show background work as running and clear unread results when seen (#728) The host now publishes #716's background-work fact as SessionState.backgroundWork, and isSessionWorking makes every running surface show an idle chat with live background commands or subagents as working instead of quietly finished. Unread results are acknowledged when actually seen: focused window, the chat is the focused one, no history overlay, Resources inspector or modal covers it, and the transcript follows the latest edge. This replaces the direct-activation gesture model. | 24 days ago | |
feat(web): first-run onboarding — intro, privacy and connect-and-verify wizard, recommendations from detected accounts, and Start a new project (#808) * squash: onboarding-first-run * feat(onboarding): harden account detection, share the consent draft between the dialog and the wizard's privacy step, let custom presets place the TODO List, and align the layout e2e specs with Balanced opening on it * fix(onboarding): advance the privacy step only after its consent save lands, verify a provider connected before the wizard reaches Connect, and record the new-project Default exception in the top-level docs * fix(onboarding): keep provider status in one store snapshot invalidated by every change, adopt a consent choice confirmed elsewhere, fence the new-project setup continuation, offer API keys on every provider, and make the onboarding e2e deterministic * test(projects): write the rollback test's hooksPath with forward slashes so git can parse it on Windows * fix(locales): drop the Spanish strings for the removed login Submit key and the reworded open-project subtitle * fix(locales): drop the Japanese strings for the removed login Submit key and the reworded open-project subtitle * Harden onboarding connect: auto-recheck empty model lists, cancel a pending login on skip, split the wizard into step components, drop the viewport-cap table, and emit a created project once * Verify a connection against an awaited model.refresh when the cached model.list is empty, instead of a fixed-delay re-read * fix(onboarding): re-read provider status on Welcome entry and on another login's success, and hand off only from a current snapshot * test(onboarding): settle the host before each cold start, run every teardown step, and drop wall-clock and inherited git-config dependence * fix(onboarding): take the connect dialog's baseline from its first report, so a sign-in that lands before the opening re-read still hands off * test(onboarding): read config.json tolerantly inside polls, remove the projects folder with retries, and count the toast before it auto-dismisses * test(shared): wait for the Central watcher's second re-arm instead of a fixed 5 ms sleep --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 10 days ago | |
Fix blinking on opening a diff and switching diff modes (#799) * fix(web): when showing diff of small files, don't blink with the skeleton but gracefully fade in * fix(web): cache git status so that we don't call it again for same changes * fix(web): fix diff blinking when switching between Stacked and One-file modes * fix(web): bound the diff caches and keep reveals until a fresh git status - gitStatusCache owns the git.status read: bounded LRU, newest request wins, removed workspaces' entries are dropped - a seeded status is provisional: a reveal, the stored active file and the Stacked restore wait for the tab's own read instead of being dropped - RenderedDiff merges are keyed by a hash of both sides in a size-bounded LRU, and a cache hit stays mounted across the next edit --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 12 days ago | |
feat(web): continuous Changes review tab with hunk triage, turn receipts and a review guide (#668) * docs(web): specify the Changes review tab presentation and its store/layout contracts * feat(web): add the changes review tab kind to the store and layout model * refactor(web): extract the shared DiffSurface from DiffPane * feat(web): add the continuous Changes review tab * test(web): cover the Changes review tab and adopt the click/double-click grammar in e2e * feat(web): track kept hunks on the changes review tab by content key * feat(web): add hunk keep triage and a progress bar to the changes review tab * test(web): cover hunk keep triage and document the overlay contract * feat(server): record per-run worktree snapshots as turn change sets with a turn diff scope * feat(web): add the Last turn diff scope and agent-turn list to Changes * feat(web): show the host's change receipt and a Review turn action under each agent round * test(web): cover turn receipts and the Last-turn scope end to end * feat(server): record the reviewer's summary and reading order as the review guide * feat(web): add the guided review rail to the changes review tab * test(web): cover the guided review walk and document the guide rail * fix(web): keep the Pierre highlight worker pool alive for the page * docs(web): record the persistent Pierre pool and review-section shape rules * fix(server): deliver turn receipts live and close the turn-tracker and review-guide races * fix(web): correct the review tab's receipts, send scope, shortcuts, triage keys and progress handling * test(web): reproduce triage progress loss on section remount * fix(review): preserve drafts and progress and close receipt and guide races * fix(server): keep a turn snapshot when git skips an unreadable file * fix(server): bound the shutdown wait for in-flight turn receipts * docs(server): record that a turn receipt spans the workspace interval * docs(web): name the Changes review tab's regions in the UI terminology * fix(web): isolate review-tab writes and harden reveal focus, scroll restore and progress styling * fix(web): open a turn's review tab without waiting for the Changes panel's status * refactor(web): give the review thread card one edit-state path on every surface * test(web): let review sections settle before the triage walk clicks Keep * fix(web): give each reading step its own guide cursor position * fix(web): let a review tab whose commit or turn is gone drop its list and offer all changes | 27 days ago | |
fix: stop the Changes review snap-back and the Pierre reveal give-up behind three e2e flakes (#1128) * fix(web): stop Virtuoso's scrollToIndex retries from undoing a review scroll Flaky e2e: changes-review-drafts "Changes retains unsaved draft-thread edits through virtualization and honors save or Escape" timed out clicking changes-review-prev (run 41203747599): the button re-rendered disabled, the walk back on file 1 after Next. Cause: react-virtuoso re-issues a scrollToIndex whenever item sizes change within 150 ms of its previous attempt, and it never stops for user input. While stacked sections still load and resize, the earlier draft-00 reveal kept being re-applied after the wheel (and after a composer's focus scroll): the list snapped back to draft-00, so the test's "draft-00 is back" assertion passed before the row click's preview-delayed reveal, which then landed after Next and reset activePath to draft-00. That breaks the documented contract that user input takes the reveal over. The list's scroller now applies scrollTo only while a reveal is settling. Evidence: with lazy chunks delayed up to 1.5 s, ~37% of 120 runs showed Virtuoso-internal scrolls (no app frames on the stack) after user input, and one run in 60 lost its wheel outright; with the gate, 0 of 120. * fix(resource-renderers): retry a Pierre line reveal until its rows are laid out Flaky e2e: changes-review "the guide reveals a finding deep inside a bounded (virtualized) section" failed toBeInViewport with ratio 0 (run 41138747897, branch run-project-button; that branch's run.spec failures are unrelated - it touched no review or Pierre code): the finding card stayed in an unassigned annotation slot. Cause: when the guide asks for the finding before Pierre's worker has answered, the section has no rows yet, so Pierre's Virtualizer.scrollTo clamps the reveal to 0 - yet revealLine reported success and usePierreFocus latched it, never revealing line 1500 once the rows landed. revealLine now reports success only when the line lies in the viewport, so the focus reveal retries when the content grows. The test now holds the Pierre worker script (holdPierreWorkers) until the finding is requested, forcing that order: 5/5 failures before the fix, 5/5 green after. * test(e2e): read pi settings only after the switch settles Flaky e2e: pi-packages "a pi package installed from Settings reaches a chat, switches off and on, and is removed" threw "Unexpected non-whitespace character after JSON at position 157 (line 8 column 4)" from readSettings inside expect.poll (run 41224170834). Cause: the poll read pi's settings.json while the host was rewriting it for the tool-search switch. readFileSync's first read returned the old 155-byte file and the next read, after the write grew it, the new file's tail - old JSON followed by ` "extensions": [...`, exactly position 157 - and expect.poll rethrows a callback error instead of polling again. Both switch probes now wait for the switch's data-active, which follows the host's reply (sent after the write) and the catalog re-list, then read the file once. A hot reader racing the toggle hit the identical torn content in 3/20 runs; reading after the switch settled saw 0 torn reads in 20 runs. * test(e2e): poll pi settings through torn reads in the extra-usage spec The extra-usage spec polled JSON.parse of pi's settings.json while the host rewrites it, the same torn read that flaked pi-packages; no UI state marks that write, so the poll now treats a SyntaxError as "not yet". The torn-read and Pierre worker-hold rules move to their own paragraph and to the fixtures paragraph of e2e/SPEC.md. | 6 days ago | |
[Design] Unify buttons into one system and move the app onto it (#817) * feat(ui): unify buttons into one system and move the app onto it packages/ui Button and IconButton become the single button system: primary, soft-primary, outline, ghost and destructive at S/M/L (20/24/32px; 14/16/20px icons), iconLeft/iconRight, square icon-only buttons whose label is also the shared tooltip, loading (icon-sized spinner, blocks re-triggering) and a persistent selected state. Labels use a new ui.button style (470 weight). Every state is a semantic token: new dangerHover/onDanger palette keys and control-destructive-* roles, a soft-primary hover role that keeps the accent label AA, an offset primary focus outline visible over every fill, ghost focus with its hover fill, pressed identical to hover, disabled never hidden. Ordinary action buttons across apps/web, resource renderers and the visualize extension move onto it (including the new i18n strings from main); panel tool rails wear the ghost M skin without the accent edge bar. Legacy variant/size names are removed and buttonUsage.test.ts rejects them, call-site skins and raw <button> re-skins. IconTooltip no longer opens on programmatic focus and closes before the layer beneath it on Escape. * docs(design-audit): add the button-system catalog and migration record buttons.html renders the production Button/IconButton in all six themes with every state, the app's selections and the real tool rails; verify-buttons.ts checks geometry, focus and contrast; buttons-migration.md records every old-to-new change and each remaining raw button with its reason. * fix(web): show the reason on a disabled remove-group button The empty-group remove IconButton wraps its tooltip trigger, so a disabled button still explains why it cannot remove the group. * docs(web): allow the prompt module's shared Button dependency in its specs * feat(ui): let a loading button spin its own refresh icon in place Button and IconButton gain spinIcon: with loading, the button rotates its own icon instead of swapping in the spinner. The five refresh controls the components loading vocabulary names (GitHub, providers, both model pickers, branch picker) use it, so their RefreshCw keeps spinning in place as specced. * fix(web): move Environment settings onto the shared button system * fix(ui): show tooltips on disabled buttons and replace title with a tooltip prop * fix(ui): raise ghost actions inside hover-filled rows to the elevated surface * fix(ui): give soft-primary a selected look and mark the expanded file chip * feat(ui): add a tone prop so warning and danger actions keep their colour in every state * fix(web): square the composer Stop button at narrow widths and give it a tooltip * refactor(ui): share button data attributes and icon wrappers, and move JSDoc into the spec * fix(ui): cover fieldset-disabled and reduced-motion loading, keep icon focus rings unclipped, lift tooltips above toasts * fix(web): restore rail drop hints and badges, the comment icon's elevation, short queue tooltips, and the Changes picker pair * docs: align the button specs and the migration log with the review fixes * fix(web): move the new Discord link onto the shared button system * docs(web): describe the current button API in the Projects and notification specs * fix(ui): wrap a tooltip trigger only while its button is disabled, and merge buttonVariants compounds * fix(web): keep history-row action hovers and Changes toolbar focus rings visible * fix(web): name the current diff scope on the Changes scope trigger for screen readers * docs(ui): note that IconButton defaults to size m * fix(web): move the custom themes folder actions onto the shared button system * fix(themes): derive dangerHover and onDanger for theme files written before they existed * fix(design-audit): initialize themes through the renamed initializeThemes export * fix(web): keep the Japanese catalog and the button guard in step with main's moved ask card --------- Co-authored-by: Julia.Shilova <Julia.Shilova@MacBook-Pro-5.local> Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 10 days ago | |
chat: keep a streaming transcript visible when the chat mounts (#636) * fix(web): keep the transcript visible when a streaming chat mounts Virtuoso's initialTopMostItemIndex hides the list until a scroll to the final row sees no size change, which never happens while that row streams. A returning or just-opened working chat stayed blank. The reading band now owns mount placement and waits until the first row mounts. A working chat holds at the uncapped Settle target through the row measurement frames, applied without animation. An idle oldest-first chat holds at the latest edge; an idle newest-first chat does not move. Native input, reader takeover, a new turn, or a controller reveal cancels the pending placement. A stream that starts during the idle hold re-places at Settle. The stream-mount e2e replays a live stream on tab return and on a just-opened chat in both orders, plus a 1000x560 tab-return case, and checks the live edge is in view on the first shown frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(e2e): widen stream-mount first-visible budget for CI runners Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(e2e): assert stream-mount hides no rendered rows instead of timing first paint The 250 ms first-visible budget measured row render time on slow runners, not the Virtuoso hide. Check the bug directly: no frame where rows exist but the latest row is not visible. Keep a loose 2 s budget and let the stream edge reach view within 500 ms on tab return. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(e2e): drop the offline-reconnect test's dependence on a WebSocket close handshake Closing the app socket after context.setOffline(true) hangs in CLOSING when socket traffic is in flight, so the client never reports disconnected. The faster transcript mount reaches that step while startup requests are still in flight. Route the socket instead: close the page side directly and refuse reconnects until the test goes back online. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: danyaberezun <danya.berezun@gmail.com> Co-authored-by: Rinat S <rinat.suleimanov@jetbrains.com> | 30 days ago | |
chore: near-zero code comments — rationale lives in specs (#269) Strip ~10.6k comments across apps/packages/e2e/scripts (parser-accurate, string/JSX-safe). What survives: biome-ignore directives, five one-line hazard notes pointing at their specs, and opaque-literal label tables (unicode controls, git ref syntax, DEC modes, chunk-error strings, line-number fixtures). Rationale not yet specced was promoted first: xterm #6065 IME rescue -> panels/SPEC.md; palette-reset ordering + alias self-reference -> apps/web/scripts/SPEC.md. Everything else checked was already recorded in the owning SPEC.md; the abandoned wsProtocol v4-v17 changelog is deleted (git history retains it). Codify the rule where agents read it: AGENTS.md ('Comments: avoid them. Near-zero is the norm.' + a 'No comment creep' handoff audit), writing-specs (specs are the only home for rationale), brainstorming step 9 (self-review checks for rationale comments). | 2 months ago | |
feat(resources): add chat-scoped command and subagent controls (#489) * docs(resources): define chat resource controls * feat(commands): add session-owned background commands * feat(resources): expose chat-scoped resource controls * feat(resources): add chat resource inspection and controls * test(resources): enforce resource module public surfaces * fix(e2e): use the active host for resource wire probes * refactor(resources): trim redundant tests and private exports * fix(agent): drain queued input during session teardown * fix(resources): harden lifecycle and current-main compatibility --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 1 month ago | |
feat(web): move the plan to a TODO List side rail, add todo_promote, and render the plan summary as a PR body (#706) * feat(pi-todos): add todo_promote for atomic loose-item promotion, with skill and spec updates * docs(pi-todos): shape the plan summary as PR body material and source shipping-a-pr from it * feat(web): Plan quick wins — inline agent message and done summary, plain-language sections and review labels, planning… line for empty groups * feat(web): move Plan to a right side-rail tool showing the compact TODO list, migrating old center-tab Plan documents * feat(web): make the TODO List rail the only plan surface — drop chat popover, header chip, and UI Remove; mute done items * fix(server): no baseline means no attribution — stop misclaiming dirty paths on bundled-done * fix(web): render the Plan summary as bounded Markdown, decoding literal escapes in agent-authored text * docs: draft RFCs for Plan v2, Plan side rail, chatless work surface, and touched-paths attribution * fix(web): fan out useChatTodos writes across rail and page via a refetch bus The rail (PlanRailPane) and the center Plan page each call useChatTodos and hold their own React state. Only agent-driven events (tool_execution_end / agent_settled) refetched both; a user-initiated add from the rail only patched its own state and the Plan page lagged until the next agent tick — timing-flaky on web no-agent, deterministically broken on binary/desktop (chat-plan.spec.ts:31 timed out 5/5 locally). Introduce a module-local pub/sub (todoRefetchBus) keyed on tupleKey("chat-todos", workspaceId, sessionId). Each hook instance subscribes on mount and reloads through the same 250ms debounce queue that already serves WS events. add / startReview / reviewAll signal the bus after their own optimistic patch, so sibling surfaces for the same chat stay in lockstep without touching the Zustand store or the transport contract. Snapshot the Set before dispatch so a React render-time subscribe that races a sibling write doesn't accidentally run for the in-flight signal (JS Set iteration otherwise picks it up). Also fix test ergonomics around the new tool-rail-plan: - Collapse the double tool-rail-plan.click() (a toggler closed the pane that had just opened) across plan-review / plan-ask / plan-open-pr / chat-plan into a single revealWorkbenchTool(page, "plan") call; extend the fixture's union with "plan" and leave a comment on why a bare click is a trap. - Update tool-rails right-rail cluster snapshots to include the new tool-rail-plan entry (parked as a trailing unplaced cluster by default, joined with the orphaned tool-rail-review after the remove-pane step). New regressions: - tool-rails: plan rail entry is an idempotent toggler + plan rail reachable by keyboard with proper aria-controls / aria-labelledby. - chat-plan: a task added from the Plan page shows up in the rail without an agent tick, an empty chat shows the empty-rail copy and no todo rows, two chats keep their own TODO lists (rail-write in chat A does not bleed into chat B, retarget on tab switch is symmetric). - useChatTodos.test: fan-out and key isolation, listener added mid-signal does not run for the signal in flight, duplicate subscribe fires once, idempotent unsubscribe, one listener's throw leaves the bus usable. * fix(pi-todos, web): address review findings on the plan rail — todo_promote collisions, todo_remove ghost groups, pending-ask glance Three findings from jetbrains-air on the PlanRailPane / pi-todos work. packages/pi-todos/core/store.ts - todo_promote (BLOCKING): now validates the normalized title BEFORE splicing the loose item. Reject an empty / whitespace-only override (a blank group has no addressable task for 'todo_add group:') and reject a collision with an existing group title (silent merge adds no step, so the second ask would vanish). The loose item stays put in both reject paths so the caller can retry with a disambiguating override. JSDoc updated to spell out the three reject cases. - todo_remove: prune a group whose last item was removed in this call, so an 'everything cleared' group doesn't leave a permanent ghost 'planning…' task that the user has no tool to delete. A promote-born empty group that was ALREADY empty before the call is preserved (the parked-user-ask invariant). Implemented by snapshotting pre-call todo counts and pruning iff count went from >0 to 0. apps/web/src/panels/PlanRailPane.tsx - Derive glance through hostSessionGlance(runtime.hostState, planGlance(…)) instead of planGlance(isStreaming, {}). The rail is now the only compact plan surface (the chat popover is gone), so when ask_user_question is pending the user needs to see 'Needs your answer' instead of 'paused'. Same path the center Plan page uses. - Expose data-glance on the pane's root so tests can lock the glance without an in_progress row mounted (StatusIcon only renders data-glance for in-progress items). Tests - pi-todos: four new unit cases — promote rejects collision (loose stays) + promote rejects empty/whitespace override + remove prunes a drained group + remove preserves a promote-born already-empty group + sanity remove-one-of-many. The previous 'promote merges into an existing group' test fixed the exact behaviour the blocking finding named as a bug, so it's replaced. - e2e: new 'a pending ask surfaces on the rail as waiting_question glance, not paused' in plan-ask.spec.ts, seeded with an awaiting ask_user_question toolResult. * fix(pi-todos, server): second-round review — spec the new promote and remove invariants, PR-body-shape the fallback plan summary Addresses the second review round on PR #706. packages/pi-todos/core/SPEC.md (BLOCKING) The authoritative core contract still said that a same-title promotion merges, which stopped being true in the previous round. Replaced the 'merges into an existing group' sentence with the full four-case reject list that the implementation now enforces (unknown id, grouped step, empty normalized title, title collision), spelled out that the loose item is left in place in every reject so the caller can retry with a disambiguating override. Extended the 'empty groups are first-class' section to cover the new 'remove-prune' half of the invariant — a promote-born empty group is preserved, a group that LOSES its last item in remove is pruned — so a reader finds the whole rule in one spot and no longer has to reconcile code and spec. packages/pi-todos/core/store.ts Refreshed the write() comment about empty groups so a future reader doesn't conclude from it that empty groups are kept unconditionally; pointed at remove() for the ghost-group half. packages/server/src/assist/assist.ts The fallback generateTodoSummary path previously asked for prose with 'no headings, no Summary: label, under ~120 words'. Since this round, every stored plan summary is treated as a PR body: the host persists whatever the fallback emits and renderPrBody prepends it verbatim. Reshaped PLAN_SUMMARY_SYSTEM to require the same three H2 sections (## Summary / ## Changes / ## Testing) with soft fallbacks for empty sections ('_no cross-cutting notes_' / '_not verified_'). Left toPlanSummary's code-fence / 'Summary:' normalization in place, with a note that H2 headings are explicitly kept. Tests - packages/server/src/assist/assist.test.ts: new fixture emits the three-section PR body and the test asserts both the returned text AND that the system prompt steers the model into that shape. - packages/server/src/todos/autoSummary.test.ts: same shape locked through the host-side persistence path. - packages/pi-todos/tools/tools.test.ts: the add→list→update→remove round-trip no longer expects a lingering empty group; the promote-born case stays covered in core/core.test.ts. * fix(web): render the em-dash in the empty TODO rail copy, not the raw '\u2014' escape A JSX text node does NOT decode backslash escapes, so '\u2014' between the words was shown to the user literally as the seven-character sequence. Use the real em-dash — in the source instead. * fix(pi-todos): collision-safe promote with full SPEC coverage Reject promotion of a loose item whose normalized title collides with an existing group (silent merge would make the second ask vanish); let the caller supply a unique title override; honor an explicit empty-title override as blank; align SPEC with the actual return shape. * fix(web): drop PlanRailPane fallback to planGlance(isStreaming, {}) Use sessionGlance(runtime) so the rail reads 'Needs your answer' from a pending ask_user_question surfaced only via the transcript, instead of showing the misleading paused/working glyph while hostState has not arrived yet. * fix(web): keep auto-summary trigger alive without rail/page mounted ChatView re-mounts useChatTodos (return discarded) so todo.generateSummary still fires on a completed plan whose rail is closed. Without this, renderPrBody prepended nothing when the user never opened the Plan surface. * fix(web, server, pi-todos): PR #706 review round 3 — rail analytics, bus hygiene, SPEC cleanup - rename PlanOpenSurface popup -> rail end-to-end (contracts, server analytics, useChatTodos, productAnalytics.test); PlanRailPane fires plan.notifyOpened("rail") once per (workspaceId, sessionId) mount so the signal the removed chat chip used to carry survives - ChatView re-mounts useChatTodos (discarded return) so the auto-summary trigger (todo.generateSummary on a completed plan with no summary) fires whenever a chat is open, not only while the rail or center-tab Plan happens to be mounted - signalTodoRefetch(key, except?) skips the originating hook via a selfListenerRef; dispatch is now try/catch-isolated per listener so a throwing sibling cannot swallow the signal, and the sibling-isolation test locks the contract - selectWorkspaceChatTab shared helper backs selectHistoryTarget + selectRailPlanSessionId - SPEC + UI-TERMINOLOGY purge of stale popover/strip references and Russian-quoted user-feedback asides; tabIcon pairs RiTaskLine/Fill for the Plan rail tool; placeholder reads 'Queue a task for the agent' - sanitizeGroup drops the double decodeEscapes that corrupted user-authored path-like titles * chore(server, web, pi-todos): drop todo.remove wire + stale RFCs, trim rationale comments - Delete `todo.remove` end to end: wire protocol, host handler, server `removeTodo` function, 3 reviewFlow tests, 1 artifacts.test race test. The UI dropped its delete affordance in the TODO-rail migration; the agent manages removal through pi-todos' own `todo_remove` tool, which goes through the pi-todos core store and never touched the server wrapper. `isItemUnderActiveReview` kept (used by other review assertions); `clearAutoCycles` dropped as dead too. - Drop stale RFC drafts that leaked into git: apps/web/src/panels/plan-{v2,as-side-rail,chatless-work-surface}-rfc.md and packages/server/src/todos/attribution-v2-rfc.md. - Trim long rationale comments on this branch down to one-liners that name what not why; the full why already lives in panels/SPEC.md, todos/SPEC.md and host/SPEC.md. Covers useChatTodos (bus docstring, dispatch, selfListenerRef, openPlan, sibling-fanout), PlanRailPane (header + glance + bg), ChatView (useChatTodos mount), selectors (shared chat-tab helper), promote (FAILURE_MESSAGES), artifacts (no-baseline branch). * fix(pi-todos): narrow decodeEscapes to write-time and skip strings with real newlines The previous rescue ran on EVERY read and expanded any literal \n/\r/\t in agent-authored text unconditionally. That silently mutated stored text that genuinely meant backslash-n: fenced code blocks, Windows paths, regex patterns, summary prose mentioning the \n character. On-disk data changed on reload without anyone writing. Narrow it to the one case it was invented for \u2014 a double-escaped summary from a model that forgot to inject real newlines: - Decode ONLY at the write boundary (add/update/promote/replaceAll/setSummary). Reads are strict pass-through: sanitize/sanitizeArtifacts no longer touch text, and old on-disk escapes stay verbatim on reload. - Decode ONLY when the string carries no real \n/\r/\t at all. A real newline is strong evidence the escapes were intentional. - Protect \\n via a negative lookbehind so an explicit "escaped backslash + n" survives even in the no-real-newline branch. - origin gate unchanged: user input is still verbatim. Spec rewritten to describe the new contract. Tests cover a rich summary with real newlines + inline \n mention, a Windows-path style note, a regex pattern, an escaped backslash + n, and a pass-through-on-read reload. * refactor(web, pi-todos, server): review-driven cleanup pass on branch Addresses the eight actionable findings from the branch self-review. Comments / tests / typing (3 items in one): - Replace literal `\u2014 / \u2026 / \u2192 / \u21d2 / \u2194` escape sequences with the actual Unicode characters in `//` and `/* ... */` comments across 8 files (16 lines). JS strings are left alone — they parse at runtime. - Rename the "self-heals on next write" test in pi-todos core to describe the actual (narrow-write-only) invariant: a legacy on-disk literal \\uXXXX is NOT decoded on read and the next write preserves it verbatim. - Type `ESCAPE_MAP` as `Record<"n"|"r"|"t", string>` and drop the unreachable `?? _m` fallback in the replace callback. - Restore the em-dash in a JSX `{/* ... */}` comment in `PlanPane` that had a stray LF. Dead-export deletion: - Drop `dropItemBaseline` from `baselines.ts` — only its own test still referenced it after the `todo.remove` wire removal. The test collapses to "removeSessionBaselines drops the whole sidecar and is idempotent". - `isItemUnderActiveReview` kept: production call sites are gone, but `planReview.test.ts` + `todoReview.test.ts` legitimately use it as the invariant probe for the fix-latch + planReviewQueue state. useChatTodos API shape: - Extract `broadcastWrite(key)` inside `useChatTodos` as the single home for the self-skip write-fanout; three sites (`add`, `startReview`, `reviewAll`) now call it instead of repeating `signalTodoRefetch(..., selfListenerRef.current ?? undefined)` verbatim. - Extract `useAutoPlanSummary(workspaceId, sessionId)` as a dedicated side-effect-only hook that keeps the auto-draft of the plan summary alive even when neither the TODO rail nor the Plan page is mounted. Subscribes directly to `WS_CHANNELS.piEvent`, fetches `todo.list` on `tool_execution_end` / `agent_settled`, and asks the host for a draft via `todo.generateSummary`; signals the refetch bus on success. - `ChatView` drops its ghost `useChatTodos` mount in favour of the new hook. No more full `todo.list` fetch + three WS subs just to arm one effect. - `PlanRailPane` drops its `eslint-disable-next-line react-hooks/exhaustive-deps`: the one-shot `plan_opened { surface: "rail" }` fire now hits `getTransport().request("todo.list", {..., opened: "rail"})` directly in the mount effect, so deps are exactly `[workspaceId, sessionId]` and no suppression is needed. React Compiler now compiles the component; baseline updated. - `notifyOpened` dropped from the public `ChatTodos` interface — no external consumer survives; the one internal use in `openPlan` was inlined as a raw transport call. * fix(web): keep plan auto-summary alive while any center tab is selected ChatView unmounts when the user switches to the Plan page or any other center tab, so mounting useAutoPlanSummary from ChatView meant the trigger silently died whenever the user was reviewing the Plan at the moment the agent finished the last step. The user would then click Open PR and renderPrBody read an empty plan.summary — the promised ## Summary / ## Changes / ## Testing body was missing. Move the mount to shell/WorkspaceWorkbench, which stays alive as long as the workspace is active. A tiny PlanSummaryAutoDraft anchor renders one useAutoPlanSummary per unique chat sessionId in the workspace's open tabs, so the trigger survives any center-tab switch for as long as the chat tab exists. Dedup across future tab kinds that reference the same sessionId falls out of the Set collection. Dropped the ChatView useAutoPlanSummary mount; updated chat/SPEC.md to describe the new owner. * fix(web): cover every session-backed tab in the plan auto-summary anchor Previous fix anchored the auto-summary trigger to `kind: "chat"` tabs only. A user who closes the chat tab to history but keeps the Plan page open (plan `doc` or the live center-tab `PlanTab`) would drop out of `chatSessionIds`, so the anchor unmounts; the agent could finish the remaining steps while the Plan page stays open and Open PR would still receive an empty plan summary. Export the existing `editorSessionId(tab)` helper from the store and use it in `WorkspaceWorkbench` to collect every unique sessionId across all session-backed tab kinds (chat / plan / doc). A new session-backed tab kind can't drift out of coverage without touching `editorSessionId` itself. * fix(pi-todos): scope markdown escape rescue to summary fields only, not every text field Reviewer bot flagged that the previous no-real-newline guard still corrupts short strings that genuinely contain backslash + n: an agent recording a title of "fix C:\new file.ts", a note like "ran /^\n/.test(x)", a verification "grep '\n' src/foo.ts", or a commit subject "fix: handle C:\new on windows" has no real newline, so decodeEscapes still fired and injected a real newline mid-string. Root cause: the rescue was gated by content (no-real-newline), not by field purpose. Titles / notes / verifications / commit subjects / artifact labels are never rendered as Markdown, so their '\n' is always backslash + n. Only the plan-level summary and the per-item summary are Markdown-rendered and need the double-escape rescue. Split decodeEscapes into: - decodeUnicode(s): \uXXXX only (unambiguous, no path/regex writes \u literally). Keeps the (?<!\\) lookbehind so \\u stays verbatim. - decodeMarkdown(s): decodeUnicode + \n/\r/\t rescue, still gated by the no-real-newline second line of defence and by (?<!\\) for \\n. Agent-write paths route per field class via decodeIfAgent (unicode-only, for titles/notes/verifications/commitSubjects/artifact-labels) or decodeMarkdownIfAgent (summary fields). Plan-level setSummary uses decodeMarkdown directly (always agent-authored, always Markdown). Core SPEC rewritten to describe the split; tests updated to pin title/note/verification/commitSubject/artifact.label as verbatim, and a new test covers a Windows-path title + regex note + grep verification + commitSubject + artifact label all surviving unchanged. * fix(pi-todos): preserve promote-born empty groups across replaceAll replaceAll carried only groups with done items from the old plan; a group with todos.length === 0 that the fresh plan didn't re-emit by title was silently dropped. The one way an empty group gets created is TodoStore.promote (parking a user ask for decomposition), so this broke the promotion contract: 'the user's ask shown as its own group survives even if the session dies right after promotion'. Carry each pre-existing empty group forward with its original id when the fresh plan doesn't name it. Tested with promote → replaceAll restructuring an unrelated group — the parked ask is still present with its original id and empty todos. Core SPEC's leftovers rule updated to call out promote-born empty groups explicitly. * fix(pi-todos, server): syntax-aware markdown decode + host-driven PR summary Two blocking issues the bot flagged against the recent decode narrowing and the auto-summary anchor: 1. The field-class split still mangled short Markdown strings whose '\n' had no real newline nearby. The bot's examples: - plan_summary 'Adjusted C:\new file' got a real newline injected between 'C:' and 'ew file' - verification '- unit → pass\n- lint → pass' stayed verbatim and rendered as one line with literal '\n' even though the planSummary tool spec treats verification as Markdown. Replace the content-based (no-real-newline) guard with a syntax-aware one that only decodes '\n' when it precedes a Markdown structural separator: - MARKDOWN_NL_PARAGRAPH: a doubled '\n\n' (paragraph break) decodes both chars in one match - MARKDOWN_NL_STRUCTURE: a standalone '\n' decodes only before bullet / ordered list / heading / blockquote / fenced block / thematic break - Everything else (short prose, Windows path, regex, code snippet) stays verbatim - '\\n' still protected via (?<!\\) - '\r' / '\t' not rescued (too rare as separators, too close to CRLF line endings and hard-tab indentation) - Markdown-rendered field class expanded to {plan.summary, item.summary, item.verification}; the previous version omitted verification despite the tool spec SPEC rewritten; test rewritten to assert the three scenarios the bot reported (Markdown structure decodes, Windows path survives, plain note stays verbatim). 2. The auto-summary anchor still mounted only for sessions represented by an open workspace tab. Closing the last chat/Plan tab to history unmounted the anchor, so a background-completed plan could still race pr.preview to an empty summary. Drive summary generation from the host side: previewPr now awaits generateTodoSummary(params) before renderPrBody reads plan.summary. generateTodoSummary is idempotent (returns existing summary, no-ops on incomplete plan, shares the in-flight promise), so a UI-triggered draft and a PR-triggered draft collapse into one generation. New test in autoSummary.test.ts covers previewPr seeding the summary on disk and prepending it to the body. * fix(server, web, contracts): normalize fallback plan summary + track rail add surface Two open non-blocking findings from the bot: 1. assist.toPlanSummary accepted any non-empty prose and persisted it verbatim, so a cheap-model response of "Done." or a dropped heading shipped through pr.preview / pr.open as a malformed PR body. Add ensurePrBodyShape(text): if the model emitted ## Summary / ## Changes / ## Testing in order, pass through; otherwise treat the whole output as the Summary body and append placeholders for the missing sections ("_no cross-cutting notes_" / "_not verified_"). splitBySections tracks each heading's start/end and extracts only the body under it, so a Summary + Testing output (skipping Changes) rebuilds in correct order instead of swallowing the trailing section into Summary. Two new assist tests cover "Done." and "Summary + Testing only"; the concurrent-draft autoSummary test updated to assert the wrapped body shape rather than the raw string. 2. plan.add from PlanRailPane defaulted surface to "chat", so plan_item_added analytics attributed every rail add to the removed chat strip and corrupted the migration's surface breakdown. Add "rail" to PlanAddSurface (server/analytics/events), to the todo.add wire surface enum (contracts/wsProtocol), and to useChatTodos.add's parameter. PlanRailPane now calls plan.add(title, "rail"). Analytics SPEC + productAnalytics.test updated so plan_item_added surfaces read [chat, page, rail, chat]. * docs(web, server): drop stale plan popup/strip wording and dead RFC links * refactor(web, pi-todos): remove dead plan-strip code and stale remove guidance * fix(server): keep fallback plan summary sections unique when Summary is missing * chore(web, pi-todos, server): trim rationale comments to near zero * docs: describe the plan as agent-maintained with user-added tasks * feat(web, server, contracts): user can remove not-yet-done TODO items from the rail Bring back a UI Remove affordance, scoped to the user's queue: pending and in_progress items get a hover-visible \u00d7 button in the TODO rail that calls plan.remove(id). Done items carry no button \u2014 completed steps are the plan's audit trail. Reviewing items are also excluded (same reason the host already guards). Agent-side todo_remove tool stays unchanged and bypasses this gate (needed for todo_promote cleanup). - packages/contracts: restore todo.remove wire protocol (channel + handler type). - packages/server: - restore removeTodo in todos.ts with a status==='done' guard that throws an explanatory error. Review-latch guard (isItemUnderActiveReview) layered on top for defense in depth. - restore host handler and dropItemBaseline (needed for the baseline cleanup side of removeTodo). - 3 new reviewFlow.test cases: pending+in_progress remove happy path, done-item rejection, unknown-id no-op. - apps/web: - useChatTodos.remove(id) with optimistic patch + rollback-on-error. - TodoList: new onRemove prop threaded TodoRows \u2192 GroupBlock / LooseList \u2192 TodoRow; TodoRow shows a group-hover \u00d7 button only when onRemove && status!=='done' && !reviewing. DoneGroup never receives onRemove, so done items can't accidentally expose it. - PlanRailPane passes plan.remove through. - SPECs updated: panels/SPEC.md describes the new affordance and the done guard; todos/SPEC.md documents the done-guard and the agent/UI path split. * fix(web): use bg-control-bg-selected for the Remove button hover The hover state used `bg-control-bg-pressed`, which is not a published semantic token. The strict `colors:check` gate rejected it (regression on both E2E no-agent and binary smoke jobs). Swap for `bg-control-bg-selected` \u2014 the one strong-state control-bg token that exists in the token set. * fix(web): useChatTodos.remove owns the failure toast; scope SPEC to the rail Two open non-blocking findings on the Remove affordance: 1. The click handler at TodoList.tsx:431 did `void onRemove(todo.id)`, which discarded a rejecting promise \u2014 a stale click (row became done between render and click) surfaced as an unhandled rejection without telling the user why removal failed. Move the toast ownership into `useChatTodos.remove` itself: on reject it rolls back the optimistic patch from authority (`reloadPlan`), toasts "Couldn't remove the step" via the store's error channel, and returns. The promise resolves either way, so `void plan.remove(id)` call sites stay safe. In-doc comment names the invariant. 2. panels/SPEC.md:965 described the Remove button as a Plan-page affordance, but `plan.remove` is only wired through `PlanRailPane`. Scope the paragraph to the TODO rail where it actually lives: moved it out of the Plan-page section into the rail section, named it "not-yet-done rail items", and spelled out that the center-tab Plan page is a review-trail surface and does NOT expose Remove. Queue manipulation lives on the rail. * fix(server, web): clear auto-review cycles on UI removal and label the remove button * docs: restore user-editable plan wording now that the rail can remove items --------- Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 23 days ago | |
feat(resources): keep finished background work out of the dock and persist each chat's history across restarts (#1004) * feat(contracts): add interrupted runs, outcome tallies and paged resource history (v96) * feat(server): add a durable per-chat archive for background commands and subagents * feat(server): serve a chat's archived resource history, paged, with logs and interrupted runs * feat(web): keep only live work in the Resources dock and fold finished history in the inspector * test(e2e): cover a chat's archived Resources history across the dock, fold, paging and interrupted runs * docs(specs): record where Resources history lives and that interrupted work stays user-facing * fix(server): mark only teardown-initiated subagent cancellations as interrupted | 9 days ago | |
feat(resources): keep finished background work out of the dock and persist each chat's history across restarts (#1004) * feat(contracts): add interrupted runs, outcome tallies and paged resource history (v96) * feat(server): add a durable per-chat archive for background commands and subagents * feat(server): serve a chat's archived resource history, paged, with logs and interrupted runs * feat(web): keep only live work in the Resources dock and fold finished history in the inspector * test(e2e): cover a chat's archived Resources history across the dock, fold, paging and interrupted runs * docs(specs): record where Resources history lives and that interrupted work stays user-facing * fix(server): mark only teardown-initiated subagent cancellations as interrupted | 9 days ago | |
feat(resources): keep finished background work out of the dock and persist each chat's history across restarts (#1004) * feat(contracts): add interrupted runs, outcome tallies and paged resource history (v96) * feat(server): add a durable per-chat archive for background commands and subagents * feat(server): serve a chat's archived resource history, paged, with logs and interrupted runs * feat(web): keep only live work in the Resources dock and fold finished history in the inspector * test(e2e): cover a chat's archived Resources history across the dock, fold, paging and interrupted runs * docs(specs): record where Resources history lives and that interrupted work stays user-facing * fix(server): mark only teardown-initiated subagent cancellations as interrupted | 9 days ago | |
chat: keep a streaming transcript visible when the chat mounts (#636) * fix(web): keep the transcript visible when a streaming chat mounts Virtuoso's initialTopMostItemIndex hides the list until a scroll to the final row sees no size change, which never happens while that row streams. A returning or just-opened working chat stayed blank. The reading band now owns mount placement and waits until the first row mounts. A working chat holds at the uncapped Settle target through the row measurement frames, applied without animation. An idle oldest-first chat holds at the latest edge; an idle newest-first chat does not move. Native input, reader takeover, a new turn, or a controller reveal cancels the pending placement. A stream that starts during the idle hold re-places at Settle. The stream-mount e2e replays a live stream on tab return and on a just-opened chat in both orders, plus a 1000x560 tab-return case, and checks the live edge is in view on the first shown frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(e2e): widen stream-mount first-visible budget for CI runners Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(e2e): assert stream-mount hides no rendered rows instead of timing first paint The 250 ms first-visible budget measured row render time on slow runners, not the Virtuoso hide. Check the bug directly: no frame where rows exist but the latest row is not visible. Keep a loose 2 s budget and let the stream edge reach view within 500 ms on tab return. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(e2e): drop the offline-reconnect test's dependence on a WebSocket close handshake Closing the app socket after context.setOffline(true) hangs in CLOSING when socket traffic is in flight, so the client never reports disconnected. The faster transcript mount reaches that step while startup requests are still in flight. Route the socket instead: close the page side directly and refuse reconnects until the test goes back online. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: danyaberezun <danya.berezun@gmail.com> Co-authored-by: Rinat S <rinat.suleimanov@jetbrains.com> | 30 days ago | |
fix(web): stop chat scrolling from jumping under the reader (#568) * docs(web): define reader-first chat scrolling * fix(web): make chat scrolling reader-first and remove hidden motion owners * test(e2e): pin smooth history scrolling and reader-first settlement * fix(web): keep chat content still through questions, diagrams and settlement * fix(web): follow streaming answers in window steps again | 1 month ago | |
test(e2e): let the chat's placement motion settle before clicking visualize card controls (#1124) A file visualization's fixed-height preview is taller than the transcript beside the default bottom terminal, so its toolbar sits above the fold at the latest edge. Playwright's scroll-into-view is not reader input, so while the chat's mount hold (or the alignment that follows content growth) is still running it restores the latest edge between mousedown and mouseup, or moves the preview iframe under the pointer, and the click is lost while the action reports success: the view toggle, design width, full screen, Open in tab and Show diff anyway never apply, and tool-step expansions in tool-file-links miss the same way. Move waitForScrollStability from chat-scroll.spec.ts into fixtures/chatScroll.ts and add clickTranscriptControl, which waits for data-scroll-moving="false" and stable scroll geometry before clicking. Both specs click every transcript control through it; the comparison tests first wait for the card's two preview frames, and the full-screen diff waits for the dialog to hide before Open in tab. | 6 days ago | |
feat: global search palette — find chats, workspaces and projects, run commands, open Settings sections (#804) * feat(contracts): add the shared tiered search matcher * feat(web): add the search palette in place of the session switcher `Mod+K`, double Shift and a topbar button open one palette that finds chats, workspaces and projects across every open project, runs commands with their shortcuts shown, deep-links Settings sections and toggles MCP servers and extensions in place. The empty query keeps the former switcher's role as the "Waiting for you" group next to Recent and Actions. Two new web sub-modules: `commands` (the typed contract an invocable action is described with; handlers stay with their owners) and `search` (props-driven palette, prefix/filter grammar, tiered ranking over the shared matcher with decay-free frecency and learned query→pick, client-local preferences, the double-Shift state machine). Shell composes the catalog, pages and row actions and injects them; `panels` import neither module. * docs(web): describe the search palette in the shell, store, notifications and module specs Also the README capability line for the palette. * test(e2e): cover the palette's MCP page toggle and the Tab/prefix rules in the search spec * fix(web): address the palette review — complete title backfill, offline terminal, IME, protocol gates, quoted filters - Chat-title backfill now covers every untitled workspace (active and context-project first) four requests at a time, re-asks a workspace at most once a minute and keeps the titles across opens, instead of stopping at the first 24 forever. - `terminal.new` is a host mutation (placement reserves on the host), so it is disabled offline. - The action panel's Enter ignores an IME composition like the main input does. - `settings.extensions` / `settings.mcp` follow the same protocol gates as their Settings sections, and the extension catalog / MCP list are requested only when the host serves them. - Quoted spans are tokenized before state filters, so `"is:running"` stays literal text. * fix(locales): drop the retired session-switcher keys from the bundled non-English catalogs * refactor(web): flatten the search palette to one finder level Drop the inline Theme, Extensions and MCP pages with their page stack, the per-row action panel, in-place toggles and the is:/has: filter grammar. Every setting row now opens its Settings section, the double-Shift row included; Mod+K inside the palette closes it; a command always runs. * fix(web): hydrate every open project's workspaces for the palette and keep the title backfill current The palette's backfill now loads the workspace list of every open project it has not seen — not only projects that already have session-state records — so a collapsed project's workspaces are searchable, and it re-reads its queue whenever the normalized snapshot gains or loses a workspace, so a palette opened during cold-start hydration catches up. * fix(web): label unnamed chats in the palette by their first prompt, as the retired switcher did * fix(web): address the palette design and code reviews — focus-proof keys, IME, bounded backfill and learning, literal quotes, Settle gate - Keys live on the dialog frame, not the input: a click on a header, the footer or the preview no longer leaves Escape, Mod+K and the arrows dead; Escape comes through Radix's `onEscapeKeyDown`, so a synthetic close-item Escape still closes and a nested layer keeps its own. - An active IME owns every key (`isComposing` or the 229 sentinel, also used by double Shift); plain Ctrl+R only is swallowed, so Ctrl+Shift+R reloads. - Title backfill: one `session.list` per workspace until its session set changes (a failed read included), workspaces whose chats are all titled are skipped, `workspace.list` skips diff stats, and both queues keep four requests in flight across palette openings (`backfillQueue`, unit-tested); the queues re-read on welcome, open-project and workspace changes. - Learning: adaptive counts are computed once per query and keyed on the text without its scope prefix; a query keeps its 32 most recent picks. - Matcher: a quoted term is a contiguous literal (no acronym or subsequence), an unterminated quote runs to the end, and case plus Latin accents fold without moving highlight offsets (İ, final sigma). - Waiting for you leaves running chats out and shows eight rows before an in-place "Show N more"; a notification click resets an already-open palette (`searchSession`). - Settle is disabled with the tree's reason while an agent works or a chat needs you (`Command.disabledReason`); the palette offers Start a new project and Open a path on the host, shows the native Settings shortcut, and says Connecting rather than Disconnected before the first welcome. - Sections are named groups; the chips and the Preview toggle use the shared Button (`aria-pressed`). - Tests: matcher, learning, sections, backfill queue, catalog gates and store session units; e2e for IME keys, focus outside the input and scope switching; double Shift is dispatched deterministically and the bottom-panel case waits for its known initial state; fixtures follow #869's input state. * fix(web): record backfilled chats in the history projection and refresh a project list that misses a reported workspace - The palette's title backfill no longer keeps a private title cache: `session.list` results go through `noteClosedChats` into the workspace's history projection (the same rows reconciliation writes), so the rename broadcast every client receives updates a chat this client never opened; a reply from an earlier connection is dropped and retried on the next schedule. - A project whose list exists but lacks a workspace the session snapshot reports (created by a peer while this client was away) is listed again, and the reply is merged with `addWorkspace` so rows that arrived meanwhile are never replaced. - `paletteBackfill.ts` holds the ordering, membership and install rules, covered with the real store: rename after backfill, missed membership, no clobbering, backfill order. * fix(web): re-list palette titles after a reconnect or a timeout, and leave Mod+Shift+I and Mod+Alt+I to the browser - A workspace's listing is keyed on the welcome generation too, so a reconnect lists it again; a timeout or a dropped connection no longer marks it listed, only a host-refused read does. - `Mod+I` toggles the preview only without Shift or Alt, so the DevTools chords pass through. - search/SPEC lists `@thinkrail/locales` among the allowed dependencies. * fix(web): keep backfilled titles current across renames in flight and reconnects, and merge listed workspaces in the host's order - A `session.list` reply is installed only if no rename landed while it was in flight (`sessionTitleRevision`, bumped on every `session_info_changed`) and the connection is the same; otherwise it is dropped and asked again. - After a reconnect, workspaces the palette listed before are listed once more, so a rename missed while away reaches their rows; the effect also re-reads when a workspace gains or loses a session. - The queue runs a key once more after it lands when it was asked for while in flight (a reply dropped across a reconnect no longer waits for the palette to reopen). - A refreshed project list keeps the host's creation order, holds on to rows that arrived meanwhile and never replaces a row the client already has. - `selectKnownChatTitle` lets an unnamed layout tab yield to the history row's title. - `createPaletteBackfill` owns the queues and listing keys with injected sources; deferred-reply tests cover a rename in flight, a reconnect, a stale reply and refusal vs timeout. * fix(web): re-ask a palette listing a rename overtook without waiting for another trigger The backfill now watches the store itself while the palette is open (`watch()`), keyed on the welcome generation, the open projects, each workspace's session count and `sessionTitleRevision`, so a listing dropped because a rename landed in flight is asked again on its own; the hook only mounts the watch. Tests drive the store instead of calling `schedule()` by hand, including a session joining an existing workspace. * fix(web): load a project's workspace list before the palette opens a chat in it A chat row can point at a workspace whose list was never loaded (the palette lists chats from the session snapshot), and picking it unmounts the palette, dropping its queued listings. Opening it now loads the project's list first when the row is missing, and still opens if that fails, so the location bar, the URL and the tree highlight see the workspace. * fix(web): open a palette pick only while it is still the latest navigation, loading the list without diff stats A chat whose workspace row is missing first loads its project's list, now without diff stats and merged in the host's order (the backfill's merge), and opens only if no later pick or navigation (workspace, Project Home or tab) happened while it loaded (`paletteNavigation.ts`, unit-tested with deferred lists). * fix(web): count only user navigation when a palette pick checks it was overtaken The overtaken check compared the active tab, which a route target from an earlier pick changes on its own when its chat finishes opening; it now compares the workspace's navigation tick, which only user navigation advances, so a legitimate pick is no longer dropped while a user tab switch still is. * fix(web): guard the palette's Work in project folder and project picks as later picks Work in project folder entered the Default workspace without a stillWanted guard, so a late workspace.list reply could pull the user back after they chose another workspace; it now passes the navigator's pick guard. A project pick also supersedes a chat pick still loading its project, so picking the Project Home already shown is not undone by an older pick landing. * fix(web): adapt the palette to main's typed tests and Russian catalog * feat: carry chat titles on session-state records and drop the palette's title backfill * fix(web): keep AltGr and Option characters, return focus on close, and drop picks a round trip overtook * refactor(web): build palette commands for their context, share the chord handlers, and drop dead code * fix(web): order Waiting by urgency, fold decomposed accents, prefer visible fields and keep odd learned keys * docs: describe the palette as it ships — commands coverage, matcher placement, shell and web edges * test(e2e): check the palette's focus return before a pick navigates | 5 days ago | |
feat(projects): clone a repository into a chosen folder and open it (#975) * feat(projects): add cancellable repository cloning * feat(web): clone repositories from the add-project menu * fix(projects): redact clone diagnostics before truncation * Map clone parent-path and destination filesystem errors to PROJECT_CLONE_INVALID * Clone into the reserved working directory so Git's failure cleanup cannot follow a swapped destination path --------- Co-authored-by: OLavrik <Olya_lavrik@inbox.ru> | 10 days ago | |
feat(desktop): open Settings with Cmd+, on macOS and Ctrl+, on Windows/Linux (#747) * feat(desktop): open Settings with Cmd+, from the macOS menu and Ctrl+, on Windows/Linux * test(web): cover Ctrl+, Settings chord matching and dispatch * feat(web): advertise the Settings shortcut in the desktop gear tooltip * test(e2e): cover the Settings shortcut on macOS, Windows, and the browser * docs(specs): document the Settings shortcut in shell and desktop specs * docs(contracts): list the open-settings native command | 22 days ago | |
feat(web): add native /compact command (#292) * feat(web): add native /compact command * fix(chat): preserve queued images during compaction * todo: Investigate the failed remote no-agent e2e check ThinkRail-Todo: 01a03dcc-8619-776f-920e-6e2325e52153/t_666bd31777bf * todo: Implement accepted fixes with targeted tests ThinkRail-Todo: 01a03f1c-7798-7f23-a5d4-c2d6eb0a5479/t_9e70d25b9d84 | 2 months ago | |
fix(agent): upgrade Pi to remove fixed compaction output caps (#834) * fix(agent): upgrade Pi to remove fixed compaction output caps Adopt upstream summary sizing and retain incomplete-summary rejection. Keep MCP parsing in parity across loading, safe edits, and fingerprint/occupancy reads. Cover actual summary requests, restored tool pairs, live compaction, and packaged extension imports. * test(agent): require captured requests in reasoning assertions * fix(agent): preserve registry tool selections across restarts * fix(agent): preserve settlement after registry write failures * docs(agent): note Kimi Code base URL migration * test(e2e): await selected diff base before invalidating it The failed-read fixture already has a README change against main. Wait for the host-confirmed doomed target before deleting it, so a late base update cannot remount the scope menu during the next click. Keep error assertions and timeouts unchanged. * fix(agent): reconcile registry extension tools from Settings at attach * docs(agent): document the attach-time registry tool reconcile --------- Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 11 days ago | |
feat(web): restore bounded passive auto-open for live/unfinished chats (#361) Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 2 months ago | |
chore(e2e): type-check the e2e tree in typecheck:root (#1064) Add e2e/tsconfig.json (root config with an e2e include) and run it as an extra tsc step of typecheck:root. Fix the stale and trivial errors it surfaced: the removed "endTurn" stop reason, untyped evaluate element parameters, unchecked index reads, ask/visualize tool arguments that must be JSON objects, and exact-optional shard options. reload-navigation.spec.ts stays excluded as the remaining to-do. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 7 days ago | |
feat(web): add native /compact command (#292) * feat(web): add native /compact command * fix(chat): preserve queued images during compaction * todo: Investigate the failed remote no-agent e2e check ThinkRail-Todo: 01a03dcc-8619-776f-920e-6e2325e52153/t_666bd31777bf * todo: Implement accepted fixes with targeted tests ThinkRail-Todo: 01a03f1c-7798-7f23-a5d4-c2d6eb0a5479/t_9e70d25b9d84 | 2 months ago | |
Settings redesign (#710) * refactor(web): redesign the Settings dialog * fix(web): restore Settings sections and render the nav once * fix(web): make collapsible model-picker groups keyboard-safe and keep hidden models out of the store * fix(web): restore subagent limits, streaming movement and file line width in the redesigned Settings * fix(web): restore Layout reset to default and validate group limits * fix(web): restore provider API-key, context, quota, GitHub and notification controls * chore(web): drop the unused ghost-success button variant and refresh the compiler census * docs(web): amend Settings, picker and store specs to the repaired redesign * test(e2e): follow the redesigned Settings and collapsible model-picker groups * fix(web): list Extensions under System in Settings * fix(web): leave System theme mode with the pair's theme for that appearance --------- Co-authored-by: Julia.Shilova <Julia.Shilova@MacBook-Pro-5.local> Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 23 days ago | |
feat(web): merge Changes into a VS Code-like Files panel (#779) * fix(server): report readDir paths with forward slashes on every OS On Windows path.relative() produced src\auth while git status and watch events use src/auth, so the Files tree could only match separator-free top-level names to changed paths. * feat(server): mark git-ignored entries in fs.readDir The host fills FileNode.gitignored from git check-ignore so the Files tree can dim ignored files and folders; a non-repository or a failed check marks nothing. * feat(web): merge Changes into a VS Code-like Files panel Files gains an All | Changed switch: Changed is the former Changes tool; All marks changed files and every ancestor folder, dims git-ignored entries, highlights and auto-reveals the active file, offers collapse-all, and remembers expanded folders and scroll per workspace across reloads. The Changes side tool is removed from the layout model and presets. * test(e2e): reveal Files in All mode and adapt specs to the merged panel revealWorkbenchTool("files") now also selects All, as "changes" selects Changed; anchored file-row regexes tolerate the new status letter; the pristine Balanced layout has Review instead of Changes; the Review panel now stays mounted beside Files, so its sections keep their expansion. * test(e2e): expect the template file to be auto-revealed in the Files tree Opening the template makes it the active file, so Files' All mode already expands .pi/prompts and highlights it; clicking the folder would fold it again. * fix(web): start the Changed list at the top when the compare target changes The Changed views' remembered scroll was tagged only with the diff scope, so after picking another target branch elsewhere the list restored an offset from the previous target's list. The scroll identity now reuses the scope + target key the highlight already uses. * fix(web): apply review findings on the Files tree - A compact folder row takes the git-ignored state of the deepest folder it represents, so outer/cache is dimmed when only cache/ is ignored. - One statusLetter (chat/planView) serves the Files tree, the Changes review tab and plan rows; untracked files read U everywhere. - Shared helpers instead of copies: lib ancestorPaths and isRecord, one reveal-Files-in-Changed-mode store patch, and the renderers' validating viewStateScrollTop. | 17 days ago | |
feat(web): edge tool rails that mirror the pane model, Minimal themes, and remount-free panel toggles (#680) Projects, Specs, Files, Changes and Review move from per-pane tab strips to persistent left/right/bottom edge rails. The rail renders the layout's group model instead of hiding it: entries come in clusters, one per pane in document order, separated by a hairline; one pressed icon per open pane; removed tools sit dimmed in a trailing cluster. Drops follow the clusters (entry halves insert into that pane, a "New pane here" zone at every boundary creates one) and the pane header title drags its tool. Terminal groups keep named session tabs, and Project Home composes the same rail and pane header. Tools and terminals never share a pane (canJoinAuxiliaryGroup): enforced in every mutation, in normalization on load and across workspaces, in passive terminal placement and preset remaps. The mixed-order anchors (beforeToolByTabId) and terminal recall (lastTerminalByGroup) go with it. Menus name a pane by its contents, hide commands that can never apply to a tab, and the layout owns New terminal / New terminal pane below. Panels no longer remount on shape changes: structural-only panel-group keys, committed sizes re-applied imperatively, and topology-dependent minimums relaxed for the one membership-change commit (useTopologySettled). Themes: the standard Light/Dark and High Contrast palettes are unchanged; the quieter Minimal palettes ship as Minimal Light / Minimal Dark. A theme is a palette and role usage never varies by theme. The rails live in the split workbench renderer (workbenchRails.tsx and friends); the interface design lab that produced the direction stays out of the repository (design/ is gitignored). The spec-graph index also skips the scratch directories other assistants keep in a project (.claude, .superpowers, .gsd, .mockups, .playwright-mcp), whose plans and generated skills carry spec-shaped frontmatter. README screenshots are re-captured from this head (the real project in an isolated host: workbench light/dark and the Specs tool), with alt text that names the edge rails. | 28 days ago | |
perf(resource-renderers): mount Monaco directly without @monaco-editor/react (#816) * perf(web): open file preview at once on single click * fix(web): hold the Review reveal while a browse row's double-click can still land * fix(web): reveal Review at once when it already shows The double-click hold only protects a browse row that the Review reveal could replace. When Review already shows, the held reveal only moved its focus request up to 250 ms late, closing whatever the user had opened in the meantime, such as a comment's delete confirmation. * fix(web): let a deferred Review reveal yield to a newer tool pick, and drop timing from its tests The Review reveal deferred for a browse row's double-click only re-checked the center selection, so a tool picked inside the window (Specs) was replaced. The reveal now snapshots the showing auxiliary tools (showingAuxiliaryKey) and skips when they changed. firstClickOnly and doubleClickPendingMs take an optional clock and scheduleReviewReveal takes the pending window, so their unit tests no longer sleep to the window boundary or inherit another test's click. preview-tabs.spec freezes the page clock instead of a 200 ms budget, plays the settle window out with clock.runFor instead of waitForTimeout(300), and waits for the released read's answer before asserting it was dropped. Specs: lib names the reveal as the third settle-window user, layout adds that the Changes tab waits for code-driven opens too, and e2e records the frozen-clock pattern. * fix(resource-renderers): jump to a review focus that arrives with the Monaco mount Opening a worktree comment now lands in the file pane. The editor's mount reveal used a smooth scroll, which can stop short before the editor's first layout; the focus is then marked handled and never retried, so the card stayed off screen. That made 'opening a far-off worktree comment scrolls its card into view in the file pane' fail about 1 run in 20, and 1 in 10 with tracing. The mount reveal now scrolls immediately; a focus on a mounted editor still scrolls smoothly. * perf(resource-renderers): load grammars per language on Oniguruma WASM * fix(resource-renderers): recover a failed grammar's provider and find nested fence embeds * fix(resource-renderers): load embeds referenced inside other embeds * fix(resource-renderers): retry a failed highlighter start instead of caching it * fix(resource-renderers): use the JS engine only without WebAssembly, and let a failed theme cost only colours Shiki's loadWasm keeps a rejected start for the page, so one failed onig.wasm fetch pinned the 4.5x slower JS engine until reload. The asset is now fetched and compiled here and Shiki only instantiates the module; a failed fetch or compile fails that start and the next file retries. The JS engine runs only where WebAssembly does not exist. The highlighter was stored before its theme was selected, so a theme that failed to load left every file plain and made each later request build another engine. Theme failures are now swallowed at start and retried by the next swap. Concurrent embed scans each report the embeds they finished that the provider lacks, so a second model no longer waits for the first model's slower chunk. Implicit HTML needs a tag at a line start or a closing tag, so a generic like Array<T> in a fence no longer pulls in html. Drops a comment that restated the spec. Tests: the fence e2e uses sh, which nothing else loads, so it exercises the embed scan; a new e2e aborts the first onig.wasm fetch and expects the next file to fetch it again; the light/dark swap test restores the default theme. * fix(resource-renderers): count inline HTML elements mid-line as an html embed Narrowing the implicit-HTML scan to line-start and closing tags missed inline void tags such as <img ...> or first<br>second, so their markup stayed plain. The scan also accepts a comment and a common inline element followed by whitespace, a slash or >, which still rejects generics like Array<T> and Map<K, V>. * perf(resource-renderers): preload the code view before the first file open * fix(web): preload the code view once per active page, warm rows only after a hover rests Hover and focus warmed each row's top-candidate renderer, so hovering a mixed tree loaded the PDF, notebook and CSV chunks. Only renderers with capabilities.preload (the code view) now load early, and a hover warms after the pointer rests 150 ms on a row; focus still warms at once. The idle preload ran on every workspace switch and in hidden or offline tabs, where a failed fetch makes Chromium cache the chunk's failure for the session. It now runs once per page and lib's whenPageActive holds it until the page is visible and online. A lazy that a mount stored while a load was in flight stayed cached after the load rejected, so the next mount reused the failure. The failed load now drops that lazy too. New unit tests pin that a stored lazy is never swapped for the direct component, that a failed in-flight lazy is retried, and that non-preloading renderers are left alone. The phone e2e no longer sleeps 2.5 s. It drives the idle schedule with page.clock, waits for a sentinel request before asserting that no editor chunk was requested, and then checks that the same path does preload at desktop width. Specs updated: web, panels, shell, lib, resource-renderers. * fix(web): recheck that the page is active right before the idle preload runs whenPageActive stopped watching once it scheduled the idle task, so a page that went offline while the idle callback, fallback timer or Files-tree gate was pending still imported the chunk, and Chromium kept the failed fetch for the session. It now takes the scheduler and the task, checks activity when the scheduled run fires, and waits for visibilitychange/online again if the page went hidden or offline. The preloaded-theme e2e restores the default theme like its siblings. * perf(resource-renderers): mount Monaco directly without @monaco-editor/react * fix(resource-renderers): skip the mount's redundant Monaco updates, and record what the direct mount costs The model and options effects re-applied on mount what createFileEditor had just set. Both now skip a first run whose inputs match the mount, so a fresh open no longer pays for a second updateOptions. The spec now gives one run's numbers (the PR's table) and the measured chunk change (3.10 MB to 3.09 MB, 12.1 kB, 3.5 kB gzip). It says why text is compared to the last applied string (the editor is read-only) and that a re-suspend inside the view's boundary would dispose the editor. e2e/SPEC.md no longer explains the hover rule with the removed wrapper. review.spec.ts wheels until it observes the card leave the viewport, instead of a toPass loop that waited 500 ms per 50 px tick. The editor.spec close/reopen test is renamed to what it checks. --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 13 days ago | |
perf(resource-renderers): mount Monaco directly without @monaco-editor/react (#816) * perf(web): open file preview at once on single click * fix(web): hold the Review reveal while a browse row's double-click can still land * fix(web): reveal Review at once when it already shows The double-click hold only protects a browse row that the Review reveal could replace. When Review already shows, the held reveal only moved its focus request up to 250 ms late, closing whatever the user had opened in the meantime, such as a comment's delete confirmation. * fix(web): let a deferred Review reveal yield to a newer tool pick, and drop timing from its tests The Review reveal deferred for a browse row's double-click only re-checked the center selection, so a tool picked inside the window (Specs) was replaced. The reveal now snapshots the showing auxiliary tools (showingAuxiliaryKey) and skips when they changed. firstClickOnly and doubleClickPendingMs take an optional clock and scheduleReviewReveal takes the pending window, so their unit tests no longer sleep to the window boundary or inherit another test's click. preview-tabs.spec freezes the page clock instead of a 200 ms budget, plays the settle window out with clock.runFor instead of waitForTimeout(300), and waits for the released read's answer before asserting it was dropped. Specs: lib names the reveal as the third settle-window user, layout adds that the Changes tab waits for code-driven opens too, and e2e records the frozen-clock pattern. * fix(resource-renderers): jump to a review focus that arrives with the Monaco mount Opening a worktree comment now lands in the file pane. The editor's mount reveal used a smooth scroll, which can stop short before the editor's first layout; the focus is then marked handled and never retried, so the card stayed off screen. That made 'opening a far-off worktree comment scrolls its card into view in the file pane' fail about 1 run in 20, and 1 in 10 with tracing. The mount reveal now scrolls immediately; a focus on a mounted editor still scrolls smoothly. * perf(resource-renderers): load grammars per language on Oniguruma WASM * fix(resource-renderers): recover a failed grammar's provider and find nested fence embeds * fix(resource-renderers): load embeds referenced inside other embeds * fix(resource-renderers): retry a failed highlighter start instead of caching it * fix(resource-renderers): use the JS engine only without WebAssembly, and let a failed theme cost only colours Shiki's loadWasm keeps a rejected start for the page, so one failed onig.wasm fetch pinned the 4.5x slower JS engine until reload. The asset is now fetched and compiled here and Shiki only instantiates the module; a failed fetch or compile fails that start and the next file retries. The JS engine runs only where WebAssembly does not exist. The highlighter was stored before its theme was selected, so a theme that failed to load left every file plain and made each later request build another engine. Theme failures are now swallowed at start and retried by the next swap. Concurrent embed scans each report the embeds they finished that the provider lacks, so a second model no longer waits for the first model's slower chunk. Implicit HTML needs a tag at a line start or a closing tag, so a generic like Array<T> in a fence no longer pulls in html. Drops a comment that restated the spec. Tests: the fence e2e uses sh, which nothing else loads, so it exercises the embed scan; a new e2e aborts the first onig.wasm fetch and expects the next file to fetch it again; the light/dark swap test restores the default theme. * fix(resource-renderers): count inline HTML elements mid-line as an html embed Narrowing the implicit-HTML scan to line-start and closing tags missed inline void tags such as <img ...> or first<br>second, so their markup stayed plain. The scan also accepts a comment and a common inline element followed by whitespace, a slash or >, which still rejects generics like Array<T> and Map<K, V>. * perf(resource-renderers): preload the code view before the first file open * fix(web): preload the code view once per active page, warm rows only after a hover rests Hover and focus warmed each row's top-candidate renderer, so hovering a mixed tree loaded the PDF, notebook and CSV chunks. Only renderers with capabilities.preload (the code view) now load early, and a hover warms after the pointer rests 150 ms on a row; focus still warms at once. The idle preload ran on every workspace switch and in hidden or offline tabs, where a failed fetch makes Chromium cache the chunk's failure for the session. It now runs once per page and lib's whenPageActive holds it until the page is visible and online. A lazy that a mount stored while a load was in flight stayed cached after the load rejected, so the next mount reused the failure. The failed load now drops that lazy too. New unit tests pin that a stored lazy is never swapped for the direct component, that a failed in-flight lazy is retried, and that non-preloading renderers are left alone. The phone e2e no longer sleeps 2.5 s. It drives the idle schedule with page.clock, waits for a sentinel request before asserting that no editor chunk was requested, and then checks that the same path does preload at desktop width. Specs updated: web, panels, shell, lib, resource-renderers. * fix(web): recheck that the page is active right before the idle preload runs whenPageActive stopped watching once it scheduled the idle task, so a page that went offline while the idle callback, fallback timer or Files-tree gate was pending still imported the chunk, and Chromium kept the failed fetch for the session. It now takes the scheduler and the task, checks activity when the scheduled run fires, and waits for visibilitychange/online again if the page went hidden or offline. The preloaded-theme e2e restores the default theme like its siblings. * perf(resource-renderers): mount Monaco directly without @monaco-editor/react * fix(resource-renderers): skip the mount's redundant Monaco updates, and record what the direct mount costs The model and options effects re-applied on mount what createFileEditor had just set. Both now skip a first run whose inputs match the mount, so a fresh open no longer pays for a second updateOptions. The spec now gives one run's numbers (the PR's table) and the measured chunk change (3.10 MB to 3.09 MB, 12.1 kB, 3.5 kB gzip). It says why text is compared to the last applied string (the editor is read-only) and that a re-suspend inside the view's boundary would dispose the editor. e2e/SPEC.md no longer explains the hover rule with the removed wrapper. review.spec.ts wheels until it observes the card leave the viewport, instead of a toPass loop that waited 500 ms per 50 px tick. The editor.spec close/reopen test is renamed to what it checks. --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 13 days ago | |
chore(e2e): type-check the e2e tree in typecheck:root (#1064) Add e2e/tsconfig.json (root config with an e2e include) and run it as an extra tsc step of typecheck:root. Fix the stale and trivial errors it surfaced: the removed "endTurn" stop reason, untyped evaluate element parameters, unchecked index reads, ask/visualize tool arguments that must be JSON objects, and exact-optional shard options. reload-navigation.spec.ts stays excluded as the remaining to-do. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 7 days ago | |
feat: choose the pi config folder and pass environment variables from Settings (#912) * feat(shared): snapshot launch env keys before the login-shell repair * feat(server): apply a stored host environment at boot * feat(web): add Settings → Environment for the pi config folder and variables * docs: document the pi config folder and environment settings * fix(server): don't report reserved environment rows as set at launch * fix(web): lock the Environment form while a save is in flight * fix(server): match environment names case-insensitively on Windows | 11 days ago | |
perf(resource-renderers): mount Monaco directly without @monaco-editor/react (#816) * perf(web): open file preview at once on single click * fix(web): hold the Review reveal while a browse row's double-click can still land * fix(web): reveal Review at once when it already shows The double-click hold only protects a browse row that the Review reveal could replace. When Review already shows, the held reveal only moved its focus request up to 250 ms late, closing whatever the user had opened in the meantime, such as a comment's delete confirmation. * fix(web): let a deferred Review reveal yield to a newer tool pick, and drop timing from its tests The Review reveal deferred for a browse row's double-click only re-checked the center selection, so a tool picked inside the window (Specs) was replaced. The reveal now snapshots the showing auxiliary tools (showingAuxiliaryKey) and skips when they changed. firstClickOnly and doubleClickPendingMs take an optional clock and scheduleReviewReveal takes the pending window, so their unit tests no longer sleep to the window boundary or inherit another test's click. preview-tabs.spec freezes the page clock instead of a 200 ms budget, plays the settle window out with clock.runFor instead of waitForTimeout(300), and waits for the released read's answer before asserting it was dropped. Specs: lib names the reveal as the third settle-window user, layout adds that the Changes tab waits for code-driven opens too, and e2e records the frozen-clock pattern. * fix(resource-renderers): jump to a review focus that arrives with the Monaco mount Opening a worktree comment now lands in the file pane. The editor's mount reveal used a smooth scroll, which can stop short before the editor's first layout; the focus is then marked handled and never retried, so the card stayed off screen. That made 'opening a far-off worktree comment scrolls its card into view in the file pane' fail about 1 run in 20, and 1 in 10 with tracing. The mount reveal now scrolls immediately; a focus on a mounted editor still scrolls smoothly. * perf(resource-renderers): load grammars per language on Oniguruma WASM * fix(resource-renderers): recover a failed grammar's provider and find nested fence embeds * fix(resource-renderers): load embeds referenced inside other embeds * fix(resource-renderers): retry a failed highlighter start instead of caching it * fix(resource-renderers): use the JS engine only without WebAssembly, and let a failed theme cost only colours Shiki's loadWasm keeps a rejected start for the page, so one failed onig.wasm fetch pinned the 4.5x slower JS engine until reload. The asset is now fetched and compiled here and Shiki only instantiates the module; a failed fetch or compile fails that start and the next file retries. The JS engine runs only where WebAssembly does not exist. The highlighter was stored before its theme was selected, so a theme that failed to load left every file plain and made each later request build another engine. Theme failures are now swallowed at start and retried by the next swap. Concurrent embed scans each report the embeds they finished that the provider lacks, so a second model no longer waits for the first model's slower chunk. Implicit HTML needs a tag at a line start or a closing tag, so a generic like Array<T> in a fence no longer pulls in html. Drops a comment that restated the spec. Tests: the fence e2e uses sh, which nothing else loads, so it exercises the embed scan; a new e2e aborts the first onig.wasm fetch and expects the next file to fetch it again; the light/dark swap test restores the default theme. * fix(resource-renderers): count inline HTML elements mid-line as an html embed Narrowing the implicit-HTML scan to line-start and closing tags missed inline void tags such as <img ...> or first<br>second, so their markup stayed plain. The scan also accepts a comment and a common inline element followed by whitespace, a slash or >, which still rejects generics like Array<T> and Map<K, V>. * perf(resource-renderers): preload the code view before the first file open * fix(web): preload the code view once per active page, warm rows only after a hover rests Hover and focus warmed each row's top-candidate renderer, so hovering a mixed tree loaded the PDF, notebook and CSV chunks. Only renderers with capabilities.preload (the code view) now load early, and a hover warms after the pointer rests 150 ms on a row; focus still warms at once. The idle preload ran on every workspace switch and in hidden or offline tabs, where a failed fetch makes Chromium cache the chunk's failure for the session. It now runs once per page and lib's whenPageActive holds it until the page is visible and online. A lazy that a mount stored while a load was in flight stayed cached after the load rejected, so the next mount reused the failure. The failed load now drops that lazy too. New unit tests pin that a stored lazy is never swapped for the direct component, that a failed in-flight lazy is retried, and that non-preloading renderers are left alone. The phone e2e no longer sleeps 2.5 s. It drives the idle schedule with page.clock, waits for a sentinel request before asserting that no editor chunk was requested, and then checks that the same path does preload at desktop width. Specs updated: web, panels, shell, lib, resource-renderers. * fix(web): recheck that the page is active right before the idle preload runs whenPageActive stopped watching once it scheduled the idle task, so a page that went offline while the idle callback, fallback timer or Files-tree gate was pending still imported the chunk, and Chromium kept the failed fetch for the session. It now takes the scheduler and the task, checks activity when the scheduled run fires, and waits for visibilitychange/online again if the page went hidden or offline. The preloaded-theme e2e restores the default theme like its siblings. * perf(resource-renderers): mount Monaco directly without @monaco-editor/react * fix(resource-renderers): skip the mount's redundant Monaco updates, and record what the direct mount costs The model and options effects re-applied on mount what createFileEditor had just set. Both now skip a first run whose inputs match the mount, so a fresh open no longer pays for a second updateOptions. The spec now gives one run's numbers (the PR's table) and the measured chunk change (3.10 MB to 3.09 MB, 12.1 kB, 3.5 kB gzip). It says why text is compared to the last applied string (the editor is read-only) and that a re-suspend inside the view's boundary would dispose the editor. e2e/SPEC.md no longer explains the hover rule with the removed wrapper. review.spec.ts wheels until it observes the card leave the viewport, instead of a toPass loop that waited 500 ms per 50 px tick. The editor.spec close/reopen test is renamed to what it checks. --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 13 days ago | |
feat(web): unified model · effort picker with host-kept favorites, connection kinds and vendor glyphs (#614) * feat(contracts): add picker metadata, favorite and recent models at protocol v77 * feat(server): persist favorite models, record recents, and project cost/modality/auth onto the catalog * feat(web): carry favorite/recent models in the store and add a shared model-preferences hook * feat(web): add the unified ModelEffortPicker pill with favorites, recents and nested effort * feat(web): put the model · effort pill in the composer and add the /model command * feat(web): use the model · effort pill with a Default row in the new-workspace dialog * docs(specs): describe the unified model · effort picker and its host-kept favorites * test(e2e): cover the model · effort pill and migrate specs to the single trigger * fix(web): keep the model picker open after a model pick and bind effort to the chosen model * feat(web): restyle the model picker as menu rows with an effort stepper, vendor glyphs and connection icons * fix(picker): close rollback and favorites races, drop guessed defaults and dead code * fix(web): size the model picker popover to the space Radix reports so the search never clips * fix(web): fence the chained effort write behind the latest model pick and switch the dialog to Default synchronously * feat(web): replace the effort stepper with a gradient slider in the model · effort picker The effort control under the model list becomes a slider: a thick rail hiding a cool→warm gradient the handle uncovers, the level word and glyph on the handle, one clickable label per level, a warning caption on the costly tiers and a footer link back to the saved default. A native range input drives drag, click-to-snap, touch and arrow keys. Pill bars take the level's tone so cost reads before the word. E2E screenshots fast-forward CSS animations so a spring mid-flight never lands in a picture. * fix(web): advance the picker's selection fence on effort picks too A direct effort change while a model request was still in flight did not supersede that request's chained level write, so B·high followed by a slide to medium could let B's response re-send high over the newer choice. One pair-selection counter now advances for every model or effort pick, and the chained write (and its failure rollback) fires only while its pick is still the newest. * fix(web): pin the effort slider's end labels to the rail ends Centering every label on its stop pushed half of a long first word ("Minimal" on models without an off level) outside the popover. The first and last labels now align with the rail's ends; the middle ones stay centered on their stops. * fix(web): clamp a session's effort through the host when its model stops offering that level After a catalog refresh a chat could sit on a level its model no longer lists, so the slider's native range snapped to the first stop while the handle, glyph and aria-valuetext still named the removed level. ChatView now asks model.clampThinking and applies the answer — the reconcile the new-workspace dialog already runs, and the clamp pi applies at request time — so the pill and slider show the level pi will actually use. * test(e2e): exercise the effort reset link against a default the test saved itself The step assumed the fixture host already carried a saved defaultEffort, which the desktop lane does not, so it waited for "reset to undefined". The link is now checked after the test's own Set-as-default: absent while the level matches, offered as "reset to high" after a slide to medium, and gone again once clicked. | 1 month ago | |
fix: list an MCP call's arguments inside its approval prompt (#763) * feat(server): carry an MCP call's input on its approval request * fix(web): list a tool call's arguments inside its approval prompt | 19 days ago | |
feat(ask-user-question): redesign the questionnaire card — stable height, notes and Other in place, rich previews, Discuss, receipt (#1192) * fix(ask-user-question): make every questionnaire control reachable by keyboard in WebKit * feat(pi-ask-user-question): add question previews, opt-in file previews, discuss-first answers and one-option confirmations * feat(extensions): add per-extension settings and share the workspace file check through the session seam * refactor(resource-renderers): share the framed file preview and add compact chrome for embedded frames * feat(ask-user-question): drive file previews from the rich-preview setting and teach the Discuss and preview conventions * refactor(ask-user-question): stack question pages at a stable height with in-place note slots and a real Other field * feat(ask-user-question): add question previews, a fixed preview pane and a per-question Discuss mark to the card * feat(ask-user-question): replace the answered record with a foldable question → answer receipt * test(ask-user-question): cover stable paging, growing fields, previews, Discuss, the receipt and the rich-preview switch in Chromium and WebKit * docs(specs): note the plan page's preview scope and the WKWebView tab-stop gap * fix(ask-user-question): keep Discuss across reconnects, let the Other checkbox cancel it, and make previews keyboard-scrollable * test(ask-user-question): click Submit only once the page-change reveal has settled * feat(ask-user-question): Enter picks, Space adds a note, d discusses and the platform chord submits in the card * feat(pi-ask-user-question): match the card's keys in the terminal — Enter picks, Space notes, Next and Submit rows, Ctrl+Enter sends * docs(ask-user-question): describe the explicit-selection keyboard model in the specs and live spec * chore(locales): restore package.json and tsconfig.json that a catalog edit rewrote * docs(web): note that the platform modifier now comes from @thinkrail/ui/keyboard * Document extension settings in the contracts, settings, host, store, transport and chat specs, move them to protocol v104 after the GitHub star ask took v103, and narrow stored setting flags with a type guard instead of a cast. * Gate Discuss on a shared ASK_DISCUSS_PROTOCOL_VERSION (v104) so a v103 host, whose formatter would report an empty discuss as no answer, never shows the toggle. * ci(e2e): fail over a stalled apt mirror when installing WebKit's system libraries * fix(server): load extensions with the setting snapshot the loader records, so a change and revert mid-reload never leaves a stale tool schema current * ci(e2e): cap each WebKit dependency install attempt so a trickling apt mirror retries instead of exhausting the wait * ci(e2e): install WebKit's missing libraries without Playwright's font group, whose mirror download stalled past the wait * Move extension settings and Discuss to protocol v105 after background workspace creation took v104 * test(e2e): assert the plan-page answer by its lasting receipt, not the transient sent state the card can leave before WebKit sees it --------- Co-authored-by: OLavrik <Olya_lavrik@inbox.ru> Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 1 day ago | |
feat: show the in-app survey in the app language from its PostHog translations (#1033) * feat(server): serve the in-app survey in the app language from its PostHog translations * feat(web): ask for the survey in the UI language and send the translation shown * fix: send a choice question's Other text separately so it is never mapped as a translated choice * fix: pin survey translations and separate Other text to protocol v98 and keep the v97 submission shape working both ways * fix(server): drop a translation's open choice before matching it to the survey's fixed choices * fix(web): keep Send disabled while no host is welcomed so a queued submission never guesses its shape | 8 days ago | |
feat(web): merge Changes into a VS Code-like Files panel (#779) * fix(server): report readDir paths with forward slashes on every OS On Windows path.relative() produced src\auth while git status and watch events use src/auth, so the Files tree could only match separator-free top-level names to changed paths. * feat(server): mark git-ignored entries in fs.readDir The host fills FileNode.gitignored from git check-ignore so the Files tree can dim ignored files and folders; a non-repository or a failed check marks nothing. * feat(web): merge Changes into a VS Code-like Files panel Files gains an All | Changed switch: Changed is the former Changes tool; All marks changed files and every ancestor folder, dims git-ignored entries, highlights and auto-reveals the active file, offers collapse-all, and remembers expanded folders and scroll per workspace across reloads. The Changes side tool is removed from the layout model and presets. * test(e2e): reveal Files in All mode and adapt specs to the merged panel revealWorkbenchTool("files") now also selects All, as "changes" selects Changed; anchored file-row regexes tolerate the new status letter; the pristine Balanced layout has Review instead of Changes; the Review panel now stays mounted beside Files, so its sections keep their expansion. * test(e2e): expect the template file to be auto-revealed in the Files tree Opening the template makes it the active file, so Files' All mode already expands .pi/prompts and highlights it; clicking the folder would fold it again. * fix(web): start the Changed list at the top when the compare target changes The Changed views' remembered scroll was tagged only with the diff scope, so after picking another target branch elsewhere the list restored an offset from the previous target's list. The scroll identity now reuses the scope + target key the highlight already uses. * fix(web): apply review findings on the Files tree - A compact folder row takes the git-ignored state of the deepest folder it represents, so outer/cache is dimmed when only cache/ is ignored. - One statusLetter (chat/planView) serves the Files tree, the Changes review tab and plan rows; untracked files read U everywhere. - Shared helpers instead of copies: lib ancestorPaths and isRecord, one reveal-Files-in-Changed-mode store patch, and the renderers' validating viewStateScrollTop. | 17 days ago | |
feat: let users choose the interface and code fonts (#1092) * feat: let users choose the interface and code fonts Appearance gains Fonts and Custom fonts groups: interface and code fonts from the bundled faces, the host's installed fonts or uploaded files, fallback fonts, code size, ligatures and a text-weight slider. typography.json stays the only source of defaults; a new web fonts module overrides only the user-adjustable tokens on :root. * fix(fonts): harden font settings after review - Render the font pickers into the Settings dialog so they scroll and stay inside it. - Keep an open Pierre comment when the code size changes: hand the new row height to the connected Pierre instances instead of remounting the surface. - Settings drafts no longer let a host echo undo an unsent edit or a newer save; xterm and Mermaid follow the code font through a latest-wins watcher; the custom font cap is checked again right before the rename. - Share the Settings folder row, draft input, save, sub-heading, tag and range slider, and one host data-folder poller for locales, themes and fonts. - Name every picker and reset for assistive tech; the preview is inert on the chat canvas. - Translate the font strings into Russian. - Make the font e2e independent of installed fonts and reset settings over the wire. * fix(fonts): wait for the resolved weight and reach the launched host in e2e - fontsLoaded loads the resolved weight too, so Monaco, xterm and Mermaid measure the face they will draw with, not the regular one. - The font e2e talks to the host the page was launched against, so it runs in the binary and desktop artifact modes. * fix(visualize): wait for the code face before the first Mermaid render | 7 days ago | |
chore: near-zero code comments — rationale lives in specs (#269) Strip ~10.6k comments across apps/packages/e2e/scripts (parser-accurate, string/JSX-safe). What survives: biome-ignore directives, five one-line hazard notes pointing at their specs, and opaque-literal label tables (unicode controls, git ref syntax, DEC modes, chunk-error strings, line-number fixtures). Rationale not yet specced was promoted first: xterm #6065 IME rescue -> panels/SPEC.md; palette-reset ordering + alias self-reference -> apps/web/scripts/SPEC.md. Everything else checked was already recorded in the owning SPEC.md; the abandoned wsProtocol v4-v17 changelog is deleted (git history retains it). Codify the rule where agents read it: AGENTS.md ('Comments: avoid them. Near-zero is the norm.' + a 'No comment creep' handoff audit), writing-specs (specs are the only home for rationale), brainstorming step 9 (self-review checks for rationale comments). | 2 months ago | |
test(e2e): make real-Central agent runs deterministic (#352) * todo: Зафиксировать одобренный дизайн в task-spec и e2e SPEC ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_ea68a25a38b5 * todo: Реализовать изолированный real-Central agent harness ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_6c99016803f9 * todo: Добавить регрессионные проверки harness ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_05797da1abbb * todo: Прогнать локальные проверки и полные e2e ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_c293b80879f5 * todo: Провести финальный review diff и подготовить коммит ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_25b97956ddef * test(e2e): stabilize live runs after rebase | 2 months ago | |
feat(chat): ask for a GitHub star once, after the tenth prompt (#1152) * feat(chat): ask for a GitHub star once, after the fifth prompt * feat(chat): translate the GitHub star ask * fix(chat): count New Workspace and plan page prompts toward the star ask * feat(chat): ask on the tenth prompt with the new copy and a live star count * feat(chat): keep the star ask up for 60 seconds and report its outcome The toast stays for 60 seconds. It reports shown, ignored, dismissed or github_click to the host, which captures an additional analytics event only while additional sharing is on. * refactor(chat): drop the star ask's analytics, live count and client counter * feat(server): ask for a GitHub star from the feedback message counter * docs(server): record the star ask in the feedback, host and contracts specs * feat(web): raise the GitHub star toast when the host asks * test(e2e): cover the host-driven GitHub star toast * feat(server): send the GitHub star count with the star ask * fix(server): read the star count only for a client that can show the ask * feat(web): show the host's star count on the GitHub star toast --------- Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 3 days ago | |
feat(chat): ask for a GitHub star once, after the tenth prompt (#1152) * feat(chat): ask for a GitHub star once, after the fifth prompt * feat(chat): translate the GitHub star ask * fix(chat): count New Workspace and plan page prompts toward the star ask * feat(chat): ask on the tenth prompt with the new copy and a live star count * feat(chat): keep the star ask up for 60 seconds and report its outcome The toast stays for 60 seconds. It reports shown, ignored, dismissed or github_click to the host, which captures an additional analytics event only while additional sharing is on. * refactor(chat): drop the star ask's analytics, live count and client counter * feat(server): ask for a GitHub star from the feedback message counter * docs(server): record the star ask in the feedback, host and contracts specs * feat(web): raise the GitHub star toast when the host asks * test(e2e): cover the host-driven GitHub star toast * feat(server): send the GitHub star count with the star ask * fix(server): read the star count only for a client that can show the ask * feat(web): show the host's star count on the GitHub star toast --------- Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 3 days ago | |
fix(shared): retry smoke teardown in our own loop, not rmSync's (#342) * fix(shared): retry smoke teardown in our own loop, not rmSync's Nightly 33166583594 failed on windows-latest again, in the same place #341 was meant to fix: `installerSmoke.ts` printed `installer smoke OK`, every assertion having passed, and then teardown threw `EBUSY` on the temp root. The retry #341 added is inert — Bun's `node:fs` `rmSync` accepts `maxRetries`/`retryDelay` and ignores them. The log proves it: the throw lands 252 ms after the success line, under a policy that mandates seconds of backoff. So the retry loop has to be ours. `@thinkrail/shared/removeTree` retries a recursive remove while the failure is one a wait can resolve (EBUSY, EMFILE, ENFILE, ENOTEMPTY, EPERM), ten attempts with a linear 100 ms backoff. It is teardown resilience, not error suppression: a tree still locked past the backoff throws, and a failure no delay can fix (EACCES, ENOTDIR, …) throws on the first attempt. Every teardown of a tree a child process ran from now goes through it — the three paths #341 touched plus the three e2e teardowns that carried the same inert `maxRetries`. Produced by AIR Automations. Name: Fix Failed Nightly release / Run: https://air.jetbrains.cloud/org/05cf1a7f-6ab5-713b-abd3-29d0c8a05e2d/automations/8de0d24e-2a90-4cba-aa5a-466f07c20026?run=97af53ab-d14b-42f1-b4b3-01aae2dcad7c * todo: Implement the smallest correct fix ThinkRail-Todo: 01a0483c-1d1c-7a0e-9dd0-259988bef698/t_f57947d4f145 * docs(specs): name the teardown helper in the server and desktop dep lists Produced by AIR Automations. Name: Fix Failed Nightly release / Run: https://air.jetbrains.cloud/org/05cf1a7f-6ab5-713b-abd3-29d0c8a05e2d/automations/8de0d24e-2a90-4cba-aa5a-466f07c20026?run=97af53ab-d14b-42f1-b4b3-01aae2dcad7c --------- Co-authored-by: jetbrains-air[bot] <274481315+jetbrains-air[bot]@users.noreply.github.com> Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 2 months ago | |
feat(workspaces): settle quiet workspaces onto a per-project shelf (#663) * docs(spec): define the settled workspace shelf across host, contracts, and web * feat(contracts): add the settled workspace lifecycle at protocol v78 * feat(server): report merged and closed branch reviews with their state * feat(server): record workspace activity, settle overrides, and review snapshots * feat(server): wire workspace activity, settle methods, and review refresh into the host * feat(web): derive the settled workspace partition and its view state in the store * feat(web): add the settled shelf, sort row, and bulk remove to the project rail * feat(web): mirror the settled shelf in the topbar workspace menu and PR chip * feat(web): add the settle idle-window control to Settings * test(e2e): cover the settled shelf journey and address rail rows by name * test(e2e): address the topbar location rows by name after the recency sort * fix(web): keep PR actions to open reviews now that the host reports merged ones * fix(workspaces): harden the settled shelf against unknown previews, refresh gaps, and re-activation * fix(branch-review): ask for an open review before paging settled ones * fix(workspaces): guard the bulk settled removal on the host and seed HEAD baselines at watch start The bulk Remove used to issue N blind workspace.remove calls from the client's snapshot; it is now one workspace.removeSettled whose host side refuses a row that a session is busy in, that the user pinned live since, or whose activity stamp moved, and tears the rest down one worktree at a time. Watchers now take a HEAD baseline the moment they start, so the first commit after a host restart counts as activity instead of merely seeding the observed sha. * fix(workspaces): judge the bulk-remove guard on review state too and close the review-fact gaps The host guard behind workspace.removeSettled now also compares the review state the client judged a row settled from, so a PR opened between preview and confirm keeps the row. Around the same facts: an activity stamp on a row that a merged/closed review currently settles is never coalesced away (it is the one fact that says "worked on since the merge"); review snapshots are fenced to the branch the lookup was made for and dropped when folder truth moves an external worktree's branch; the never-pushed fallback of the removal preview counts commits no remote-tracking ref reaches instead of the Changes panel's compare base; a history jump into another workspace judges the destination through the shared activation patch instead of inheriting the previous row's live latch; and workspace.openReview answers a client older than the settled protocol with null for a merged or closed review, since such a client reads any review as an open PR. * fix(workspaces): clear stale review on external branch change * fix(workspaces): close settled lifecycle race gaps * fix(web): preserve settled workspace UI through reconnect * fix(workspaces): fence final bulk removal races * fix(pr): persist review against pushed branch * refactor(workspaces): drop bulk settled removal pending a safe teardown * perf(branch-review): inspect remotes asynchronously so lookups never block the host * feat(workspaces): default the settle idle window to a week * feat(web): announce the shelf's first automatic move once per browser * docs: add the Settled shelf to the README and refresh the workbench screenshots | 29 days ago | |
feat(web): make workbench layouts frontend-local (#338) * todo: Finalize the approved design in specs ThinkRail-Todo: 01a044d3-9a12-7509-af23-71c3d56d27b9/t_5f2882d7a49b * todo: Implement normalized frame and workspace-view model ThinkRail-Todo: 01a044d3-9a12-7509-af23-71c3d56d27b9/t_b27828ff5dfb * todo: Replace layout sync with local persisted state ThinkRail-Todo: 01a044d3-9a12-7509-af23-71c3d56d27b9/t_ee4adf4ae3a0 * todo: Adapt workbench integrations and settings ThinkRail-Todo: 01a044d3-9a12-7509-af23-71c3d56d27b9/t_b2ef73c8067c * todo: Convert host and wire to preset-only synchronization ThinkRail-Todo: 01a044d3-9a12-7509-af23-71c3d56d27b9/t_ac7ecebb9d03 * todo: Update unit and browser coverage ThinkRail-Todo: 01a044d3-9a12-7509-af23-71c3d56d27b9/t_c8fc45594903 * todo: Run full gates and review the complete diff ThinkRail-Todo: 01a044d3-9a12-7509-af23-71c3d56d27b9/t_23f22ed4375a * fix(web): harden local layout isolation * fix(web): define the local layout reset default * fix: remove legacy layout migration shims * fix(server): discard retired layout settings updates --------- Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 2 months ago | |
fix(web): close history search overlay on toggle and outside click (#796) * fix(web): toggle history search overlay from the composer button * fix(web): close history search overlay on outside click * Route the history button toggle through the dismiss path * Document click-outside dismissal for the history overlay * chat: mark the history trigger with a dedicated data attribute * chat: scope outside-press exclusions to the trigger and scope menu refs * chat: drop the history-toggle rationale comment * chat: document the scoped outside-press exclusions * e2e: cover history toggle, outside-press dismissal, and scope picking * chat: keep focus on dismissal path for outside presses * chat: keep the clicked outside target's default behavior on dismissal * e2e: assert caret lands in the composer and outside controls still act on dismissal * fix(web): treat the history overlay as an fzf-style picker: presses stand down for the scope menu, focus returns only when lost --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 13 days ago | |
chore: near-zero code comments — rationale lives in specs (#269) Strip ~10.6k comments across apps/packages/e2e/scripts (parser-accurate, string/JSX-safe). What survives: biome-ignore directives, five one-line hazard notes pointing at their specs, and opaque-literal label tables (unicode controls, git ref syntax, DEC modes, chunk-error strings, line-number fixtures). Rationale not yet specced was promoted first: xterm #6065 IME rescue -> panels/SPEC.md; palette-reset ordering + alias self-reference -> apps/web/scripts/SPEC.md. Everything else checked was already recorded in the owning SPEC.md; the abandoned wsProtocol v4-v17 changelog is deleted (git history retains it). Codify the rule where agents read it: AGENTS.md ('Comments: avoid them. Near-zero is the norm.' + a 'No comment creep' handoff audit), writing-specs (specs are the only home for rationale), brainstorming step 9 (self-review checks for rationale comments). | 2 months ago | |
feat(state): replace session activity with normalized session lifecycle (#525) * docs(activity): define deletion-only compatibility * refactor(activity): remove server activity mechanisms * refactor(activity): remove web activity presentation * test(activity): retire workspace activity coverage * docs(activity): remove stale public surface * refactor(workspaces): remove the orphaned all-workspace record read * docs(state): define normalized session lifecycle * feat(state): add normalized session lifecycle * fix(state): harden receipt and nudge races * fix(web): derive plan glance from host state * fix(agent): ignore live internal session rewrites * fix(web): preserve legacy todo nudges * fix(web): restore approved session attention visuals * fix(web): pin approved attention presentation * fix(web): clear attention after direct chat open * test(web): harden direct-open completion race coverage * docs(web): count workspace entry as completion activation * fix(web): activate visible chat on workspace entry * test(web): cover workspace-entry completion receipts * fix(web): expire stale workspace read intent * fix(web): bind cold activation to first state snapshot * fix(web): rebind reconnect activation to snapshot * fix(web): fold buffered state before activation binding * docs(protocol): move session state capability to v73 * refactor(server): drop unused internal-session purpose model * fix(web): count review and plan open-chat actions as chat activation * test(e2e): wait for the new chat before opening chat history * docs(state): align session-state spec wording with the code | 1 month ago | |
Loader redesign (#727) * feat(web): update RunningIcon loader with split frame animation - Replace single-frame loader with split frame design (fa/fb halves) - Each logo stroke now clipped to its part of the contour - Varying stroke widths: frame 40px, s1/s2/c2 40px, a1/b1 46px, c3 44px - Animation cycle 3.263s with delay-based synchronization - Remove dimmed background - pure stroke animation * refactor(web): derive RunningIcon clip paths from canonical BRAND_MARK_CLIP_PATHS - Add BRAND_MARK_CLIP_PATHS to constants/branding.ts with named regions - Update RunningIcon.tsx to import clip paths from branding.ts - Update components/SPEC.md to document current split-frame animation - Remove obsolete train/pulse description from spec * refactor(web): derive the brand mark and loader clip regions from one segment source * refactor(web): share the loader frame keyframe and name the strokes after the letter parts * docs(web): record the split-frame loader and its single brand-geometry source in the specs * test(web): pin the loader's reduced-motion badge and shared mark geometry, and fix the live hydrate spec --------- Co-authored-by: Julia.Shilova <Julia.Shilova@MacBook-Pro-5.local> Co-authored-by: Rinat S <rinat.suleimanov@jetbrains.com> | 23 days ago | |
fix(e2e): keep macOS awake during browser runs (#389) | 2 months ago | |
fix(e2e): keep macOS awake during browser runs (#389) | 2 months ago | |
fix(web): let an active IME own Enter, Escape and arrows in every text field (#879) * feat(ui): add a shared IME composition guard and keep dialogs and popovers open on a composing Escape * fix(web): let an active IME own Enter, Escape and arrows in every text field (#875) * fix(web): yield the package-install and custom-endpoint fields to an active IME * test(ui): fail when a text-field key handler skips the IME composition guard * test(e2e): cover IME composition in the TODO field, chat-tab rename and Create workspace dialog * docs(specs): record that an active IME owns every key in a text field * refactor(web): use the shared IME guard in the search palette --------- Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 5 days ago | |
feat(web): translate the interface and let users add their own languages (#788) * feat(web): translate the interface and let users add their own languages * test(i18n): pass the translator in tests main added for usage counters and hydration * fix(server): reattach the locale watchers when Linux drops them with a deleted folder * fix(resource-renderers): retranslate the editor's comment controls without remounting * fix(resource-renderers): install the current comment label when the editor mounts late * fix(server): poll the locales folder instead of watching it * fix(server): catch permission repairs and mtime-preserving rewrites in the locales poller * fix(locales): let plural forms show or omit the count and fill missing forms from _other * fix(i18n): stop terminal-group labels rendering their raw key and split the Done status from the Done action * fix(web): translate default chat, terminal, Changes, diff and plan tab names at render instead of storing them * fix(i18n): translate theme names, the default workspace, thinking levels and the remaining UI labels * fix(chat): word the round closer, failure notices and durations at render instead of storing English text * fix(chat): export the plan Markdown in the interface language * fix(desktop): translate the native startup and update error dialogs * fix(i18n): keep extension notices' text, report untitled sessions without an English title, and word the default workspace everywhere * docs(specs): record that stored names carry no translated text and the plural-form rules --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 16 days ago | |
test(e2e): stop the layout tab-drag and side-focus races (#1121) * test(e2e): release tab drags only once the drop target is active "another window cannot cancel or rearrange an active tab drag" flaked on CI with center-group Expected 2, Received 1 and no cancel toast. The peer window was not involved. The six-step move to "Split right" puts its fifth point about 1.4 px left of the target, so only the final move enters it, and the test released right after that move. dnd-kit drops on the `over` target its renders commit after a move (render, passive effect, setOver, render), not on the release point. A pointerup that outran those renders dropped on the stale `over` (none): a silent no-op. Throttling page 1's CPU 6x reproduced it 7/20; dispatching the final move and the release in one task reproduced it 10/10. Wait for `data-drop-active="true"` before releasing, as dragTabToTarget already does. Apply the same wait to the three other committed tab drops in layout.spec.ts that released without it; the split in "pointer drag exposes deterministic split targets" failed 2/15 under the same throttle. Record the rule in e2e/SPEC.md. * test(e2e): wait for the shown side's focus before focusing inside it Showing a side moves focus to its selected tab, here the Projects rail control, from a requestAnimationFrame scheduled after the side renders (useLayoutFocus). The hide-focus test focused the left nav's project-expand button as soon as the nav was visible. Locally that frame lands only 6-21 ms before the test's focus(); when it came late on CI it moved focus to the rail control after the test's focus() and the button read "inactive" for the whole assertion timeout (Binary and Desktop e2e, 2026-10-26, on SHAs that already carry #827). Wait for the rail control to hold focus first. Holding animation frames after Mod+B and releasing them between focus() and the assertion reproduced the CI error 30/30 before this change and passes 30/30 after. e2e/SPEC.md records the rule for probes that focus inside a just-shown region. | 6 days ago | |
Settings redesign (#710) * refactor(web): redesign the Settings dialog * fix(web): restore Settings sections and render the nav once * fix(web): make collapsible model-picker groups keyboard-safe and keep hidden models out of the store * fix(web): restore subagent limits, streaming movement and file line width in the redesigned Settings * fix(web): restore Layout reset to default and validate group limits * fix(web): restore provider API-key, context, quota, GitHub and notification controls * chore(web): drop the unused ghost-success button variant and refresh the compiler census * docs(web): amend Settings, picker and store specs to the repaired redesign * test(e2e): follow the redesigned Settings and collapsible model-picker groups * fix(web): list Extensions under System in Settings * fix(web): leave System theme mode with the pair's theme for that appearance --------- Co-authored-by: Julia.Shilova <Julia.Shilova@MacBook-Pro-5.local> Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 23 days ago | |
refactor(web): extract the file and diff renderers into @thinkrail/resource-renderers (#783) * refactor(web): extract the file and diff renderers into @thinkrail/resource-renderers * docs(specs): mark the resource-renderers spec active * refactor(web): pass renderer host markdown and code as values, preferences separately * perf(web): subscribe review-draft scratch per slot so a keystroke re-renders only its composer * perf(web): build Monaco file options only when their inputs change * fix(web): keep the rendered markdown diff on screen while a refreshed merge is pending * refactor(web): drop leftovers of the renderer extraction * docs(specs): describe the static renderer host, per-slot review drafts, and fix drift from the move --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 17 days ago | |
chore: near-zero code comments — rationale lives in specs (#269) Strip ~10.6k comments across apps/packages/e2e/scripts (parser-accurate, string/JSX-safe). What survives: biome-ignore directives, five one-line hazard notes pointing at their specs, and opaque-literal label tables (unicode controls, git ref syntax, DEC modes, chunk-error strings, line-number fixtures). Rationale not yet specced was promoted first: xterm #6065 IME rescue -> panels/SPEC.md; palette-reset ordering + alias self-reference -> apps/web/scripts/SPEC.md. Everything else checked was already recorded in the owning SPEC.md; the abandoned wsProtocol v4-v17 changelog is deleted (git history retains it). Codify the rule where agents read it: AGENTS.md ('Comments: avoid them. Near-zero is the norm.' + a 'No comment creep' handoff audit), writing-specs (specs are the only home for rationale), brainstorming step 9 (self-review checks for rationale comments). | 2 months ago | |
feat(web): edge tool rails that mirror the pane model, Minimal themes, and remount-free panel toggles (#680) Projects, Specs, Files, Changes and Review move from per-pane tab strips to persistent left/right/bottom edge rails. The rail renders the layout's group model instead of hiding it: entries come in clusters, one per pane in document order, separated by a hairline; one pressed icon per open pane; removed tools sit dimmed in a trailing cluster. Drops follow the clusters (entry halves insert into that pane, a "New pane here" zone at every boundary creates one) and the pane header title drags its tool. Terminal groups keep named session tabs, and Project Home composes the same rail and pane header. Tools and terminals never share a pane (canJoinAuxiliaryGroup): enforced in every mutation, in normalization on load and across workspaces, in passive terminal placement and preset remaps. The mixed-order anchors (beforeToolByTabId) and terminal recall (lastTerminalByGroup) go with it. Menus name a pane by its contents, hide commands that can never apply to a tab, and the layout owns New terminal / New terminal pane below. Panels no longer remount on shape changes: structural-only panel-group keys, committed sizes re-applied imperatively, and topology-dependent minimums relaxed for the one membership-change commit (useTopologySettled). Themes: the standard Light/Dark and High Contrast palettes are unchanged; the quieter Minimal palettes ship as Minimal Light / Minimal Dark. A theme is a palette and role usage never varies by theme. The rails live in the split workbench renderer (workbenchRails.tsx and friends); the interface design lab that produced the direction stays out of the repository (design/ is gitignored). The spec-graph index also skips the scratch directories other assistants keep in a project (.claude, .superpowers, .gsd, .mockups, .playwright-mcp), whose plans and generated skills carry spec-shaped frontmatter. README screenshots are re-captured from this head (the real project in an isolated host: workbench light/dark and the Specs tool), with alt text that names the edge rails. | 28 days ago | |
feat(web): edge tool rails that mirror the pane model, Minimal themes, and remount-free panel toggles (#680) Projects, Specs, Files, Changes and Review move from per-pane tab strips to persistent left/right/bottom edge rails. The rail renders the layout's group model instead of hiding it: entries come in clusters, one per pane in document order, separated by a hairline; one pressed icon per open pane; removed tools sit dimmed in a trailing cluster. Drops follow the clusters (entry halves insert into that pane, a "New pane here" zone at every boundary creates one) and the pane header title drags its tool. Terminal groups keep named session tabs, and Project Home composes the same rail and pane header. Tools and terminals never share a pane (canJoinAuxiliaryGroup): enforced in every mutation, in normalization on load and across workspaces, in passive terminal placement and preset remaps. The mixed-order anchors (beforeToolByTabId) and terminal recall (lastTerminalByGroup) go with it. Menus name a pane by its contents, hide commands that can never apply to a tab, and the layout owns New terminal / New terminal pane below. Panels no longer remount on shape changes: structural-only panel-group keys, committed sizes re-applied imperatively, and topology-dependent minimums relaxed for the one membership-change commit (useTopologySettled). Themes: the standard Light/Dark and High Contrast palettes are unchanged; the quieter Minimal palettes ship as Minimal Light / Minimal Dark. A theme is a palette and role usage never varies by theme. The rails live in the split workbench renderer (workbenchRails.tsx and friends); the interface design lab that produced the direction stays out of the repository (design/ is gitignored). The spec-graph index also skips the scratch directories other assistants keep in a project (.claude, .superpowers, .gsd, .mockups, .playwright-mcp), whose plans and generated skills carry spec-shaped frontmatter. README screenshots are re-captured from this head (the real project in an isolated host: workbench light/dark and the Specs tool), with alt text that names the edge rails. | 28 days ago | |
feat(web): edge tool rails that mirror the pane model, Minimal themes, and remount-free panel toggles (#680) Projects, Specs, Files, Changes and Review move from per-pane tab strips to persistent left/right/bottom edge rails. The rail renders the layout's group model instead of hiding it: entries come in clusters, one per pane in document order, separated by a hairline; one pressed icon per open pane; removed tools sit dimmed in a trailing cluster. Drops follow the clusters (entry halves insert into that pane, a "New pane here" zone at every boundary creates one) and the pane header title drags its tool. Terminal groups keep named session tabs, and Project Home composes the same rail and pane header. Tools and terminals never share a pane (canJoinAuxiliaryGroup): enforced in every mutation, in normalization on load and across workspaces, in passive terminal placement and preset remaps. The mixed-order anchors (beforeToolByTabId) and terminal recall (lastTerminalByGroup) go with it. Menus name a pane by its contents, hide commands that can never apply to a tab, and the layout owns New terminal / New terminal pane below. Panels no longer remount on shape changes: structural-only panel-group keys, committed sizes re-applied imperatively, and topology-dependent minimums relaxed for the one membership-change commit (useTopologySettled). Themes: the standard Light/Dark and High Contrast palettes are unchanged; the quieter Minimal palettes ship as Minimal Light / Minimal Dark. A theme is a palette and role usage never varies by theme. The rails live in the split workbench renderer (workbenchRails.tsx and friends); the interface design lab that produced the direction stays out of the repository (design/ is gitignored). The spec-graph index also skips the scratch directories other assistants keep in a project (.claude, .superpowers, .gsd, .mockups, .playwright-mcp), whose plans and generated skills carry spec-shaped frontmatter. README screenshots are re-captured from this head (the real project in an isolated host: workbench light/dark and the Specs tool), with alt text that names the edge rails. | 28 days ago | |
fix: list an MCP call's arguments inside its approval prompt (#763) * feat(server): carry an MCP call's input on its approval request * fix(web): list a tool call's arguments inside its approval prompt | 19 days ago | |
feat: ask extension dialogs inside the chat and add MCP "Always allow" (#749) * feat(server): anchor extension dialogs to their tool call and cancel them on a user message * feat(server): add a per-project "Always allow" grant for MCP tools Approvals offer Always allow, saved in the project record and keyed to a hash of the server's command/args or URL; mcp.revokeToolGrant and McpServerSummary.alwaysAllowed (protocol 84) back the Settings list. Pending dialogs are now cancelled only once pi accepts the user's message. * feat(web): ask extension dialogs inside the chat and list always-allowed MCP tools Replaces the ExtUiDialog modal with ExtUiPrompt: anchored on the tool's card (popping the awaiting step out of a collapsed fold) or docked above the composer, with the shared attention line, a one-shot announcement, wrapping/stacking options, a bounded dock and a cancel hint. Settings › MCP servers lists always-allowed tools with a per-tool revoke. * docs(specs): record in-chat extension dialogs and MCP Always allow | 20 days ago | |
chore(e2e): type-check the e2e tree in typecheck:root (#1064) Add e2e/tsconfig.json (root config with an e2e include) and run it as an extra tsc step of typecheck:root. Fix the stale and trivial errors it surfaced: the removed "endTurn" stop reason, untyped evaluate element parameters, unchecked index reads, ask/visualize tool arguments that must be JSON objects, and exact-optional shard options. reload-navigation.spec.ts stays excluded as the remaining to-do. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 7 days ago | |
Settings redesign (#710) * refactor(web): redesign the Settings dialog * fix(web): restore Settings sections and render the nav once * fix(web): make collapsible model-picker groups keyboard-safe and keep hidden models out of the store * fix(web): restore subagent limits, streaming movement and file line width in the redesigned Settings * fix(web): restore Layout reset to default and validate group limits * fix(web): restore provider API-key, context, quota, GitHub and notification controls * chore(web): drop the unused ghost-success button variant and refresh the compiler census * docs(web): amend Settings, picker and store specs to the repaired redesign * test(e2e): follow the redesigned Settings and collapsible model-picker groups * fix(web): list Extensions under System in Settings * fix(web): leave System theme mode with the pair's theme for that appearance --------- Co-authored-by: Julia.Shilova <Julia.Shilova@MacBook-Pro-5.local> Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 23 days ago | |
Settings redesign (#710) * refactor(web): redesign the Settings dialog * fix(web): restore Settings sections and render the nav once * fix(web): make collapsible model-picker groups keyboard-safe and keep hidden models out of the store * fix(web): restore subagent limits, streaming movement and file line width in the redesigned Settings * fix(web): restore Layout reset to default and validate group limits * fix(web): restore provider API-key, context, quota, GitHub and notification controls * chore(web): drop the unused ghost-success button variant and refresh the compiler census * docs(web): amend Settings, picker and store specs to the repaired redesign * test(e2e): follow the redesigned Settings and collapsible model-picker groups * fix(web): list Extensions under System in Settings * fix(web): leave System theme mode with the pair's theme for that appearance --------- Co-authored-by: Julia.Shilova <Julia.Shilova@MacBook-Pro-5.local> Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 23 days ago | |
feat(web): make workbench layouts frontend-local (#338) * todo: Finalize the approved design in specs ThinkRail-Todo: 01a044d3-9a12-7509-af23-71c3d56d27b9/t_5f2882d7a49b * todo: Implement normalized frame and workspace-view model ThinkRail-Todo: 01a044d3-9a12-7509-af23-71c3d56d27b9/t_b27828ff5dfb * todo: Replace layout sync with local persisted state ThinkRail-Todo: 01a044d3-9a12-7509-af23-71c3d56d27b9/t_ee4adf4ae3a0 * todo: Adapt workbench integrations and settings ThinkRail-Todo: 01a044d3-9a12-7509-af23-71c3d56d27b9/t_b2ef73c8067c * todo: Convert host and wire to preset-only synchronization ThinkRail-Todo: 01a044d3-9a12-7509-af23-71c3d56d27b9/t_ac7ecebb9d03 * todo: Update unit and browser coverage ThinkRail-Todo: 01a044d3-9a12-7509-af23-71c3d56d27b9/t_c8fc45594903 * todo: Run full gates and review the complete diff ThinkRail-Todo: 01a044d3-9a12-7509-af23-71c3d56d27b9/t_23f22ed4375a * fix(web): harden local layout isolation * fix(web): define the local layout reset default * fix: remove legacy layout migration shims * fix(server): discard retired layout settings updates --------- Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 2 months ago | |
test(e2e): align live coverage with canonical chat behavior (#379) | 2 months ago | |
feat(web): add Mod+N / Mod+Alt+N shortcut to create a workspace (#554) | 1 month ago | |
Settings redesign (#710) * refactor(web): redesign the Settings dialog * fix(web): restore Settings sections and render the nav once * fix(web): make collapsible model-picker groups keyboard-safe and keep hidden models out of the store * fix(web): restore subagent limits, streaming movement and file line width in the redesigned Settings * fix(web): restore Layout reset to default and validate group limits * fix(web): restore provider API-key, context, quota, GitHub and notification controls * chore(web): drop the unused ghost-success button variant and refresh the compiler census * docs(web): amend Settings, picker and store specs to the repaired redesign * test(e2e): follow the redesigned Settings and collapsible model-picker groups * fix(web): list Extensions under System in Settings * fix(web): leave System theme mode with the pair's theme for that appearance --------- Co-authored-by: Julia.Shilova <Julia.Shilova@MacBook-Pro-5.local> Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 23 days ago | |
fix(workspaces): create worktrees in the background so a large checkout never freezes the app (#1202) * feat(contracts): add workspace creation requests and pushes * feat(contracts): add workspace creation requests and pushes * test(contracts): sort the workspace creation version import * fix(workspaces): create worktrees as an async, resumable background job * fix(workspaces): keep leftovers and taken folders safe on dismiss and retry * feat(server): start the held chat when a background workspace creation finishes * fix(workspaces): never clean up a folder ThinkRail has open, and latch dismissals * feat(web): show background workspace creations in the sidebar and workspace view * fix(web): report background kickoff failures and only claim cleanups that happened * docs(specs): document background workspace creation # Conflicts: # apps/web/src/transport/SPEC.md * fix(server): wait for the workspace watcher before starting a creation's chat * fix(workspaces): guard creation folders from project open and stop orphaned checkouts before resuming # Conflicts: # packages/server/src/workspaces/workspaces.ts * refactor(workspaces): retry a creation from scratch instead of resuming a crashed checkout * fix(workspaces): refuse to retry into a folder ThinkRail has since opened * fix(workspaces): keep submodule and post-checkout behaviour of worktree add in the split checkout * fix(workspaces): fire post-checkout on git without `git hook run` * fix(server): open a new workspace's chat once its prompt passes preflight, and replay a cut-short handoff * fix(git): detect a git without `git hook` independently of the user's locale * fix(workspaces): clean up only what a creation provably made, and isolate runner callbacks * fix(server): guard every project door against a folder that is still being created * docs(web): note the legacy create fallback has no sidebar pending row * docs(specs): move background workspace creation to protocol v104 | 1 day ago | |
feat(web): first-run onboarding — intro, privacy and connect-and-verify wizard, recommendations from detected accounts, and Start a new project (#808) * squash: onboarding-first-run * feat(onboarding): harden account detection, share the consent draft between the dialog and the wizard's privacy step, let custom presets place the TODO List, and align the layout e2e specs with Balanced opening on it * fix(onboarding): advance the privacy step only after its consent save lands, verify a provider connected before the wizard reaches Connect, and record the new-project Default exception in the top-level docs * fix(onboarding): keep provider status in one store snapshot invalidated by every change, adopt a consent choice confirmed elsewhere, fence the new-project setup continuation, offer API keys on every provider, and make the onboarding e2e deterministic * test(projects): write the rollback test's hooksPath with forward slashes so git can parse it on Windows * fix(locales): drop the Spanish strings for the removed login Submit key and the reworded open-project subtitle * fix(locales): drop the Japanese strings for the removed login Submit key and the reworded open-project subtitle * Harden onboarding connect: auto-recheck empty model lists, cancel a pending login on skip, split the wizard into step components, drop the viewport-cap table, and emit a created project once * Verify a connection against an awaited model.refresh when the cached model.list is empty, instead of a fixed-delay re-read * fix(onboarding): re-read provider status on Welcome entry and on another login's success, and hand off only from a current snapshot * test(onboarding): settle the host before each cold start, run every teardown step, and drop wall-clock and inherited git-config dependence * fix(onboarding): take the connect dialog's baseline from its first report, so a sign-in that lands before the opening re-read still hands off * test(onboarding): read config.json tolerantly inside polls, remove the projects folder with retries, and count the toast before it auto-dismisses * test(shared): wait for the Central watcher's second re-arm instead of a fixed 5 ms sleep --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 10 days ago | |
feat(web): continuous Changes review tab with hunk triage, turn receipts and a review guide (#668) * docs(web): specify the Changes review tab presentation and its store/layout contracts * feat(web): add the changes review tab kind to the store and layout model * refactor(web): extract the shared DiffSurface from DiffPane * feat(web): add the continuous Changes review tab * test(web): cover the Changes review tab and adopt the click/double-click grammar in e2e * feat(web): track kept hunks on the changes review tab by content key * feat(web): add hunk keep triage and a progress bar to the changes review tab * test(web): cover hunk keep triage and document the overlay contract * feat(server): record per-run worktree snapshots as turn change sets with a turn diff scope * feat(web): add the Last turn diff scope and agent-turn list to Changes * feat(web): show the host's change receipt and a Review turn action under each agent round * test(web): cover turn receipts and the Last-turn scope end to end * feat(server): record the reviewer's summary and reading order as the review guide * feat(web): add the guided review rail to the changes review tab * test(web): cover the guided review walk and document the guide rail * fix(web): keep the Pierre highlight worker pool alive for the page * docs(web): record the persistent Pierre pool and review-section shape rules * fix(server): deliver turn receipts live and close the turn-tracker and review-guide races * fix(web): correct the review tab's receipts, send scope, shortcuts, triage keys and progress handling * test(web): reproduce triage progress loss on section remount * fix(review): preserve drafts and progress and close receipt and guide races * fix(server): keep a turn snapshot when git skips an unreadable file * fix(server): bound the shutdown wait for in-flight turn receipts * docs(server): record that a turn receipt spans the workspace interval * docs(web): name the Changes review tab's regions in the UI terminology * fix(web): isolate review-tab writes and harden reveal focus, scroll restore and progress styling * fix(web): open a turn's review tab without waiting for the Changes panel's status * refactor(web): give the review thread card one edit-state path on every surface * test(web): let review sections settle before the triage walk clicks Keep * fix(web): give each reading step its own guide cursor position * fix(web): let a review tab whose commit or turn is gone drop its list and offer all changes | 27 days ago | |
feat(extensions): install, view and switch pi packages from Settings → Extensions (#1025) * feat(server): add a read-only pi package catalog with pi's identity rule * feat(server): add pi package install, remove, update and resource toggles in pi's settings format * feat(server): add the disabledPackages setting and drop a switched-off package's resources from every loader * feat(server): refresh chats and pages when pi package declarations or installs change * feat(contracts,server): expose packages.* methods and the packages.changed channel * feat(web): list installed pi packages, loose extensions and pi built-ins on the Extensions page * feat(web): add the Install pi package dialog * test(e2e): cover the pi package lifecycle in the browser and with a live agent * docs(specs): record pi package management across the agent, host, contracts, web and pi-compatibility specs * fix(packages): address the comprehensive review — pi filter semantics, settings view for disabled packages, protocol gate, built-in scope, batching * fix(packages): agreement-round fixes — exact-override toggles, scoped load errors, package-free probes, honest install failures * fix(packages): keep the explicit +path on re-enable; guard the chip memo against an in-flight probe * chore(packages): trim code rationale to one-line notes; the specs own it * fix(packages): pin manifest-omitted kinds when a declaration gains filters; accept file:// sources; reconcile after a failed remove or update * chore(packages): move to protocol v98 after the rebase; restore a brace lost in conflict resolution * feat(web): drop the post-install package switch and label install scopes Global / This repository only * feat(packages): accept pi.dev and npm links, pasted pi install lines and bare npm names in the Install field * feat(web): explain accepted install sources in a tooltip and drop the dialog's trust warning * chore(packages): move to protocol v99 after the rebase * fix(packages): never throw on a malformed package URL; honour pi install -l; prefer an existing folder over a bare npm name * feat(packages): share a repository-local package's switch across worktrees; say the switch is ThinkRail-only * refactor(packages): open pi settings only through createPiSettings; a package view refuses package writes * feat(packages): take tool and command chips from what chats loaded; probe only once at install * feat(packages): defer in-package resource toggles; put a repository autoload:false override on the user row * feat(packages): list a declared package that is missing on disk as Not installed with Remove * refactor(agent): restore pi's own extension discovery when no Central path is excluded * refactor(web): move the package controller into usePiPackages and guard the update check by generation * docs(packages): rewrite the agent/packages SPEC as invariants and decisions; count the pi facts * fix(packages): read pasted commands with shell quoting; close the switch over pi's identity; re-list after a chat records chips; a Not installed row is not a successful install * fix(packages): rows carry every switch key that covers them; the page renders the host's enabled state and clears all keys on enable * docs(store): package rows render the host's enabled state; disabledPackages only feeds the next settings write * fix(packages): qualify a repository-local switch key by its repository; expand pi's git shorthands; match absolute overrides; read BOM settings; make an off-and-on flip during a reload stale * feat(locales): translate the Extensions and pi package strings into Russian; move the old Extensions description to its new key in every catalog * docs(contracts): record the pi packages protocol version as v100 after the provider-warnings bump * fix(server): open the extra-usage warning's pi settings through createPiSettings * test(web): omit optional package fields instead of passing undefined under the test typecheck | 7 days ago | |
fix: stop the Changes review snap-back and the Pierre reveal give-up behind three e2e flakes (#1128) * fix(web): stop Virtuoso's scrollToIndex retries from undoing a review scroll Flaky e2e: changes-review-drafts "Changes retains unsaved draft-thread edits through virtualization and honors save or Escape" timed out clicking changes-review-prev (run 41203747599): the button re-rendered disabled, the walk back on file 1 after Next. Cause: react-virtuoso re-issues a scrollToIndex whenever item sizes change within 150 ms of its previous attempt, and it never stops for user input. While stacked sections still load and resize, the earlier draft-00 reveal kept being re-applied after the wheel (and after a composer's focus scroll): the list snapped back to draft-00, so the test's "draft-00 is back" assertion passed before the row click's preview-delayed reveal, which then landed after Next and reset activePath to draft-00. That breaks the documented contract that user input takes the reveal over. The list's scroller now applies scrollTo only while a reveal is settling. Evidence: with lazy chunks delayed up to 1.5 s, ~37% of 120 runs showed Virtuoso-internal scrolls (no app frames on the stack) after user input, and one run in 60 lost its wheel outright; with the gate, 0 of 120. * fix(resource-renderers): retry a Pierre line reveal until its rows are laid out Flaky e2e: changes-review "the guide reveals a finding deep inside a bounded (virtualized) section" failed toBeInViewport with ratio 0 (run 41138747897, branch run-project-button; that branch's run.spec failures are unrelated - it touched no review or Pierre code): the finding card stayed in an unassigned annotation slot. Cause: when the guide asks for the finding before Pierre's worker has answered, the section has no rows yet, so Pierre's Virtualizer.scrollTo clamps the reveal to 0 - yet revealLine reported success and usePierreFocus latched it, never revealing line 1500 once the rows landed. revealLine now reports success only when the line lies in the viewport, so the focus reveal retries when the content grows. The test now holds the Pierre worker script (holdPierreWorkers) until the finding is requested, forcing that order: 5/5 failures before the fix, 5/5 green after. * test(e2e): read pi settings only after the switch settles Flaky e2e: pi-packages "a pi package installed from Settings reaches a chat, switches off and on, and is removed" threw "Unexpected non-whitespace character after JSON at position 157 (line 8 column 4)" from readSettings inside expect.poll (run 41224170834). Cause: the poll read pi's settings.json while the host was rewriting it for the tool-search switch. readFileSync's first read returned the old 155-byte file and the next read, after the write grew it, the new file's tail - old JSON followed by ` "extensions": [...`, exactly position 157 - and expect.poll rethrows a callback error instead of polling again. Both switch probes now wait for the switch's data-active, which follows the host's reply (sent after the write) and the catalog re-list, then read the file once. A hot reader racing the toggle hit the identical torn content in 3/20 runs; reading after the switch settled saw 0 torn reads in 20 runs. * test(e2e): poll pi settings through torn reads in the extra-usage spec The extra-usage spec polled JSON.parse of pi's settings.json while the host rewrites it, the same torn read that flaked pi-packages; no UI state marks that write, so the poll now treats a SyntaxError as "not yet". The torn-read and Pierre worker-hold rules move to their own paragraph and to the fixtures paragraph of e2e/SPEC.md. | 6 days ago | |
feat(ask-user-question): redesign the questionnaire card — stable height, notes and Other in place, rich previews, Discuss, receipt (#1192) * fix(ask-user-question): make every questionnaire control reachable by keyboard in WebKit * feat(pi-ask-user-question): add question previews, opt-in file previews, discuss-first answers and one-option confirmations * feat(extensions): add per-extension settings and share the workspace file check through the session seam * refactor(resource-renderers): share the framed file preview and add compact chrome for embedded frames * feat(ask-user-question): drive file previews from the rich-preview setting and teach the Discuss and preview conventions * refactor(ask-user-question): stack question pages at a stable height with in-place note slots and a real Other field * feat(ask-user-question): add question previews, a fixed preview pane and a per-question Discuss mark to the card * feat(ask-user-question): replace the answered record with a foldable question → answer receipt * test(ask-user-question): cover stable paging, growing fields, previews, Discuss, the receipt and the rich-preview switch in Chromium and WebKit * docs(specs): note the plan page's preview scope and the WKWebView tab-stop gap * fix(ask-user-question): keep Discuss across reconnects, let the Other checkbox cancel it, and make previews keyboard-scrollable * test(ask-user-question): click Submit only once the page-change reveal has settled * feat(ask-user-question): Enter picks, Space adds a note, d discusses and the platform chord submits in the card * feat(pi-ask-user-question): match the card's keys in the terminal — Enter picks, Space notes, Next and Submit rows, Ctrl+Enter sends * docs(ask-user-question): describe the explicit-selection keyboard model in the specs and live spec * chore(locales): restore package.json and tsconfig.json that a catalog edit rewrote * docs(web): note that the platform modifier now comes from @thinkrail/ui/keyboard * Document extension settings in the contracts, settings, host, store, transport and chat specs, move them to protocol v104 after the GitHub star ask took v103, and narrow stored setting flags with a type guard instead of a cast. * Gate Discuss on a shared ASK_DISCUSS_PROTOCOL_VERSION (v104) so a v103 host, whose formatter would report an empty discuss as no answer, never shows the toggle. * ci(e2e): fail over a stalled apt mirror when installing WebKit's system libraries * fix(server): load extensions with the setting snapshot the loader records, so a change and revert mid-reload never leaves a stale tool schema current * ci(e2e): cap each WebKit dependency install attempt so a trickling apt mirror retries instead of exhausting the wait * ci(e2e): install WebKit's missing libraries without Playwright's font group, whose mirror download stalled past the wait * Move extension settings and Discuss to protocol v105 after background workspace creation took v104 * test(e2e): assert the plan-page answer by its lasting receipt, not the transient sent state the card can leave before WebKit sees it --------- Co-authored-by: OLavrik <Olya_lavrik@inbox.ru> Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 1 day ago | |
[Design] Unify buttons into one system and move the app onto it (#817) * feat(ui): unify buttons into one system and move the app onto it packages/ui Button and IconButton become the single button system: primary, soft-primary, outline, ghost and destructive at S/M/L (20/24/32px; 14/16/20px icons), iconLeft/iconRight, square icon-only buttons whose label is also the shared tooltip, loading (icon-sized spinner, blocks re-triggering) and a persistent selected state. Labels use a new ui.button style (470 weight). Every state is a semantic token: new dangerHover/onDanger palette keys and control-destructive-* roles, a soft-primary hover role that keeps the accent label AA, an offset primary focus outline visible over every fill, ghost focus with its hover fill, pressed identical to hover, disabled never hidden. Ordinary action buttons across apps/web, resource renderers and the visualize extension move onto it (including the new i18n strings from main); panel tool rails wear the ghost M skin without the accent edge bar. Legacy variant/size names are removed and buttonUsage.test.ts rejects them, call-site skins and raw <button> re-skins. IconTooltip no longer opens on programmatic focus and closes before the layer beneath it on Escape. * docs(design-audit): add the button-system catalog and migration record buttons.html renders the production Button/IconButton in all six themes with every state, the app's selections and the real tool rails; verify-buttons.ts checks geometry, focus and contrast; buttons-migration.md records every old-to-new change and each remaining raw button with its reason. * fix(web): show the reason on a disabled remove-group button The empty-group remove IconButton wraps its tooltip trigger, so a disabled button still explains why it cannot remove the group. * docs(web): allow the prompt module's shared Button dependency in its specs * feat(ui): let a loading button spin its own refresh icon in place Button and IconButton gain spinIcon: with loading, the button rotates its own icon instead of swapping in the spinner. The five refresh controls the components loading vocabulary names (GitHub, providers, both model pickers, branch picker) use it, so their RefreshCw keeps spinning in place as specced. * fix(web): move Environment settings onto the shared button system * fix(ui): show tooltips on disabled buttons and replace title with a tooltip prop * fix(ui): raise ghost actions inside hover-filled rows to the elevated surface * fix(ui): give soft-primary a selected look and mark the expanded file chip * feat(ui): add a tone prop so warning and danger actions keep their colour in every state * fix(web): square the composer Stop button at narrow widths and give it a tooltip * refactor(ui): share button data attributes and icon wrappers, and move JSDoc into the spec * fix(ui): cover fieldset-disabled and reduced-motion loading, keep icon focus rings unclipped, lift tooltips above toasts * fix(web): restore rail drop hints and badges, the comment icon's elevation, short queue tooltips, and the Changes picker pair * docs: align the button specs and the migration log with the review fixes * fix(web): move the new Discord link onto the shared button system * docs(web): describe the current button API in the Projects and notification specs * fix(ui): wrap a tooltip trigger only while its button is disabled, and merge buttonVariants compounds * fix(web): keep history-row action hovers and Changes toolbar focus rings visible * fix(web): name the current diff scope on the Changes scope trigger for screen readers * docs(ui): note that IconButton defaults to size m * fix(web): move the custom themes folder actions onto the shared button system * fix(themes): derive dangerHover and onDanger for theme files written before they existed * fix(design-audit): initialize themes through the renamed initializeThemes export * fix(web): keep the Japanese catalog and the button guard in step with main's moved ask card --------- Co-authored-by: Julia.Shilova <Julia.Shilova@MacBook-Pro-5.local> Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 10 days ago | |
feat(web): move the plan to a TODO List side rail, add todo_promote, and render the plan summary as a PR body (#706) * feat(pi-todos): add todo_promote for atomic loose-item promotion, with skill and spec updates * docs(pi-todos): shape the plan summary as PR body material and source shipping-a-pr from it * feat(web): Plan quick wins — inline agent message and done summary, plain-language sections and review labels, planning… line for empty groups * feat(web): move Plan to a right side-rail tool showing the compact TODO list, migrating old center-tab Plan documents * feat(web): make the TODO List rail the only plan surface — drop chat popover, header chip, and UI Remove; mute done items * fix(server): no baseline means no attribution — stop misclaiming dirty paths on bundled-done * fix(web): render the Plan summary as bounded Markdown, decoding literal escapes in agent-authored text * docs: draft RFCs for Plan v2, Plan side rail, chatless work surface, and touched-paths attribution * fix(web): fan out useChatTodos writes across rail and page via a refetch bus The rail (PlanRailPane) and the center Plan page each call useChatTodos and hold their own React state. Only agent-driven events (tool_execution_end / agent_settled) refetched both; a user-initiated add from the rail only patched its own state and the Plan page lagged until the next agent tick — timing-flaky on web no-agent, deterministically broken on binary/desktop (chat-plan.spec.ts:31 timed out 5/5 locally). Introduce a module-local pub/sub (todoRefetchBus) keyed on tupleKey("chat-todos", workspaceId, sessionId). Each hook instance subscribes on mount and reloads through the same 250ms debounce queue that already serves WS events. add / startReview / reviewAll signal the bus after their own optimistic patch, so sibling surfaces for the same chat stay in lockstep without touching the Zustand store or the transport contract. Snapshot the Set before dispatch so a React render-time subscribe that races a sibling write doesn't accidentally run for the in-flight signal (JS Set iteration otherwise picks it up). Also fix test ergonomics around the new tool-rail-plan: - Collapse the double tool-rail-plan.click() (a toggler closed the pane that had just opened) across plan-review / plan-ask / plan-open-pr / chat-plan into a single revealWorkbenchTool(page, "plan") call; extend the fixture's union with "plan" and leave a comment on why a bare click is a trap. - Update tool-rails right-rail cluster snapshots to include the new tool-rail-plan entry (parked as a trailing unplaced cluster by default, joined with the orphaned tool-rail-review after the remove-pane step). New regressions: - tool-rails: plan rail entry is an idempotent toggler + plan rail reachable by keyboard with proper aria-controls / aria-labelledby. - chat-plan: a task added from the Plan page shows up in the rail without an agent tick, an empty chat shows the empty-rail copy and no todo rows, two chats keep their own TODO lists (rail-write in chat A does not bleed into chat B, retarget on tab switch is symmetric). - useChatTodos.test: fan-out and key isolation, listener added mid-signal does not run for the signal in flight, duplicate subscribe fires once, idempotent unsubscribe, one listener's throw leaves the bus usable. * fix(pi-todos, web): address review findings on the plan rail — todo_promote collisions, todo_remove ghost groups, pending-ask glance Three findings from jetbrains-air on the PlanRailPane / pi-todos work. packages/pi-todos/core/store.ts - todo_promote (BLOCKING): now validates the normalized title BEFORE splicing the loose item. Reject an empty / whitespace-only override (a blank group has no addressable task for 'todo_add group:') and reject a collision with an existing group title (silent merge adds no step, so the second ask would vanish). The loose item stays put in both reject paths so the caller can retry with a disambiguating override. JSDoc updated to spell out the three reject cases. - todo_remove: prune a group whose last item was removed in this call, so an 'everything cleared' group doesn't leave a permanent ghost 'planning…' task that the user has no tool to delete. A promote-born empty group that was ALREADY empty before the call is preserved (the parked-user-ask invariant). Implemented by snapshotting pre-call todo counts and pruning iff count went from >0 to 0. apps/web/src/panels/PlanRailPane.tsx - Derive glance through hostSessionGlance(runtime.hostState, planGlance(…)) instead of planGlance(isStreaming, {}). The rail is now the only compact plan surface (the chat popover is gone), so when ask_user_question is pending the user needs to see 'Needs your answer' instead of 'paused'. Same path the center Plan page uses. - Expose data-glance on the pane's root so tests can lock the glance without an in_progress row mounted (StatusIcon only renders data-glance for in-progress items). Tests - pi-todos: four new unit cases — promote rejects collision (loose stays) + promote rejects empty/whitespace override + remove prunes a drained group + remove preserves a promote-born already-empty group + sanity remove-one-of-many. The previous 'promote merges into an existing group' test fixed the exact behaviour the blocking finding named as a bug, so it's replaced. - e2e: new 'a pending ask surfaces on the rail as waiting_question glance, not paused' in plan-ask.spec.ts, seeded with an awaiting ask_user_question toolResult. * fix(pi-todos, server): second-round review — spec the new promote and remove invariants, PR-body-shape the fallback plan summary Addresses the second review round on PR #706. packages/pi-todos/core/SPEC.md (BLOCKING) The authoritative core contract still said that a same-title promotion merges, which stopped being true in the previous round. Replaced the 'merges into an existing group' sentence with the full four-case reject list that the implementation now enforces (unknown id, grouped step, empty normalized title, title collision), spelled out that the loose item is left in place in every reject so the caller can retry with a disambiguating override. Extended the 'empty groups are first-class' section to cover the new 'remove-prune' half of the invariant — a promote-born empty group is preserved, a group that LOSES its last item in remove is pruned — so a reader finds the whole rule in one spot and no longer has to reconcile code and spec. packages/pi-todos/core/store.ts Refreshed the write() comment about empty groups so a future reader doesn't conclude from it that empty groups are kept unconditionally; pointed at remove() for the ghost-group half. packages/server/src/assist/assist.ts The fallback generateTodoSummary path previously asked for prose with 'no headings, no Summary: label, under ~120 words'. Since this round, every stored plan summary is treated as a PR body: the host persists whatever the fallback emits and renderPrBody prepends it verbatim. Reshaped PLAN_SUMMARY_SYSTEM to require the same three H2 sections (## Summary / ## Changes / ## Testing) with soft fallbacks for empty sections ('_no cross-cutting notes_' / '_not verified_'). Left toPlanSummary's code-fence / 'Summary:' normalization in place, with a note that H2 headings are explicitly kept. Tests - packages/server/src/assist/assist.test.ts: new fixture emits the three-section PR body and the test asserts both the returned text AND that the system prompt steers the model into that shape. - packages/server/src/todos/autoSummary.test.ts: same shape locked through the host-side persistence path. - packages/pi-todos/tools/tools.test.ts: the add→list→update→remove round-trip no longer expects a lingering empty group; the promote-born case stays covered in core/core.test.ts. * fix(web): render the em-dash in the empty TODO rail copy, not the raw '\u2014' escape A JSX text node does NOT decode backslash escapes, so '\u2014' between the words was shown to the user literally as the seven-character sequence. Use the real em-dash — in the source instead. * fix(pi-todos): collision-safe promote with full SPEC coverage Reject promotion of a loose item whose normalized title collides with an existing group (silent merge would make the second ask vanish); let the caller supply a unique title override; honor an explicit empty-title override as blank; align SPEC with the actual return shape. * fix(web): drop PlanRailPane fallback to planGlance(isStreaming, {}) Use sessionGlance(runtime) so the rail reads 'Needs your answer' from a pending ask_user_question surfaced only via the transcript, instead of showing the misleading paused/working glyph while hostState has not arrived yet. * fix(web): keep auto-summary trigger alive without rail/page mounted ChatView re-mounts useChatTodos (return discarded) so todo.generateSummary still fires on a completed plan whose rail is closed. Without this, renderPrBody prepended nothing when the user never opened the Plan surface. * fix(web, server, pi-todos): PR #706 review round 3 — rail analytics, bus hygiene, SPEC cleanup - rename PlanOpenSurface popup -> rail end-to-end (contracts, server analytics, useChatTodos, productAnalytics.test); PlanRailPane fires plan.notifyOpened("rail") once per (workspaceId, sessionId) mount so the signal the removed chat chip used to carry survives - ChatView re-mounts useChatTodos (discarded return) so the auto-summary trigger (todo.generateSummary on a completed plan with no summary) fires whenever a chat is open, not only while the rail or center-tab Plan happens to be mounted - signalTodoRefetch(key, except?) skips the originating hook via a selfListenerRef; dispatch is now try/catch-isolated per listener so a throwing sibling cannot swallow the signal, and the sibling-isolation test locks the contract - selectWorkspaceChatTab shared helper backs selectHistoryTarget + selectRailPlanSessionId - SPEC + UI-TERMINOLOGY purge of stale popover/strip references and Russian-quoted user-feedback asides; tabIcon pairs RiTaskLine/Fill for the Plan rail tool; placeholder reads 'Queue a task for the agent' - sanitizeGroup drops the double decodeEscapes that corrupted user-authored path-like titles * chore(server, web, pi-todos): drop todo.remove wire + stale RFCs, trim rationale comments - Delete `todo.remove` end to end: wire protocol, host handler, server `removeTodo` function, 3 reviewFlow tests, 1 artifacts.test race test. The UI dropped its delete affordance in the TODO-rail migration; the agent manages removal through pi-todos' own `todo_remove` tool, which goes through the pi-todos core store and never touched the server wrapper. `isItemUnderActiveReview` kept (used by other review assertions); `clearAutoCycles` dropped as dead too. - Drop stale RFC drafts that leaked into git: apps/web/src/panels/plan-{v2,as-side-rail,chatless-work-surface}-rfc.md and packages/server/src/todos/attribution-v2-rfc.md. - Trim long rationale comments on this branch down to one-liners that name what not why; the full why already lives in panels/SPEC.md, todos/SPEC.md and host/SPEC.md. Covers useChatTodos (bus docstring, dispatch, selfListenerRef, openPlan, sibling-fanout), PlanRailPane (header + glance + bg), ChatView (useChatTodos mount), selectors (shared chat-tab helper), promote (FAILURE_MESSAGES), artifacts (no-baseline branch). * fix(pi-todos): narrow decodeEscapes to write-time and skip strings with real newlines The previous rescue ran on EVERY read and expanded any literal \n/\r/\t in agent-authored text unconditionally. That silently mutated stored text that genuinely meant backslash-n: fenced code blocks, Windows paths, regex patterns, summary prose mentioning the \n character. On-disk data changed on reload without anyone writing. Narrow it to the one case it was invented for \u2014 a double-escaped summary from a model that forgot to inject real newlines: - Decode ONLY at the write boundary (add/update/promote/replaceAll/setSummary). Reads are strict pass-through: sanitize/sanitizeArtifacts no longer touch text, and old on-disk escapes stay verbatim on reload. - Decode ONLY when the string carries no real \n/\r/\t at all. A real newline is strong evidence the escapes were intentional. - Protect \\n via a negative lookbehind so an explicit "escaped backslash + n" survives even in the no-real-newline branch. - origin gate unchanged: user input is still verbatim. Spec rewritten to describe the new contract. Tests cover a rich summary with real newlines + inline \n mention, a Windows-path style note, a regex pattern, an escaped backslash + n, and a pass-through-on-read reload. * refactor(web, pi-todos, server): review-driven cleanup pass on branch Addresses the eight actionable findings from the branch self-review. Comments / tests / typing (3 items in one): - Replace literal `\u2014 / \u2026 / \u2192 / \u21d2 / \u2194` escape sequences with the actual Unicode characters in `//` and `/* ... */` comments across 8 files (16 lines). JS strings are left alone — they parse at runtime. - Rename the "self-heals on next write" test in pi-todos core to describe the actual (narrow-write-only) invariant: a legacy on-disk literal \\uXXXX is NOT decoded on read and the next write preserves it verbatim. - Type `ESCAPE_MAP` as `Record<"n"|"r"|"t", string>` and drop the unreachable `?? _m` fallback in the replace callback. - Restore the em-dash in a JSX `{/* ... */}` comment in `PlanPane` that had a stray LF. Dead-export deletion: - Drop `dropItemBaseline` from `baselines.ts` — only its own test still referenced it after the `todo.remove` wire removal. The test collapses to "removeSessionBaselines drops the whole sidecar and is idempotent". - `isItemUnderActiveReview` kept: production call sites are gone, but `planReview.test.ts` + `todoReview.test.ts` legitimately use it as the invariant probe for the fix-latch + planReviewQueue state. useChatTodos API shape: - Extract `broadcastWrite(key)` inside `useChatTodos` as the single home for the self-skip write-fanout; three sites (`add`, `startReview`, `reviewAll`) now call it instead of repeating `signalTodoRefetch(..., selfListenerRef.current ?? undefined)` verbatim. - Extract `useAutoPlanSummary(workspaceId, sessionId)` as a dedicated side-effect-only hook that keeps the auto-draft of the plan summary alive even when neither the TODO rail nor the Plan page is mounted. Subscribes directly to `WS_CHANNELS.piEvent`, fetches `todo.list` on `tool_execution_end` / `agent_settled`, and asks the host for a draft via `todo.generateSummary`; signals the refetch bus on success. - `ChatView` drops its ghost `useChatTodos` mount in favour of the new hook. No more full `todo.list` fetch + three WS subs just to arm one effect. - `PlanRailPane` drops its `eslint-disable-next-line react-hooks/exhaustive-deps`: the one-shot `plan_opened { surface: "rail" }` fire now hits `getTransport().request("todo.list", {..., opened: "rail"})` directly in the mount effect, so deps are exactly `[workspaceId, sessionId]` and no suppression is needed. React Compiler now compiles the component; baseline updated. - `notifyOpened` dropped from the public `ChatTodos` interface — no external consumer survives; the one internal use in `openPlan` was inlined as a raw transport call. * fix(web): keep plan auto-summary alive while any center tab is selected ChatView unmounts when the user switches to the Plan page or any other center tab, so mounting useAutoPlanSummary from ChatView meant the trigger silently died whenever the user was reviewing the Plan at the moment the agent finished the last step. The user would then click Open PR and renderPrBody read an empty plan.summary — the promised ## Summary / ## Changes / ## Testing body was missing. Move the mount to shell/WorkspaceWorkbench, which stays alive as long as the workspace is active. A tiny PlanSummaryAutoDraft anchor renders one useAutoPlanSummary per unique chat sessionId in the workspace's open tabs, so the trigger survives any center-tab switch for as long as the chat tab exists. Dedup across future tab kinds that reference the same sessionId falls out of the Set collection. Dropped the ChatView useAutoPlanSummary mount; updated chat/SPEC.md to describe the new owner. * fix(web): cover every session-backed tab in the plan auto-summary anchor Previous fix anchored the auto-summary trigger to `kind: "chat"` tabs only. A user who closes the chat tab to history but keeps the Plan page open (plan `doc` or the live center-tab `PlanTab`) would drop out of `chatSessionIds`, so the anchor unmounts; the agent could finish the remaining steps while the Plan page stays open and Open PR would still receive an empty plan summary. Export the existing `editorSessionId(tab)` helper from the store and use it in `WorkspaceWorkbench` to collect every unique sessionId across all session-backed tab kinds (chat / plan / doc). A new session-backed tab kind can't drift out of coverage without touching `editorSessionId` itself. * fix(pi-todos): scope markdown escape rescue to summary fields only, not every text field Reviewer bot flagged that the previous no-real-newline guard still corrupts short strings that genuinely contain backslash + n: an agent recording a title of "fix C:\new file.ts", a note like "ran /^\n/.test(x)", a verification "grep '\n' src/foo.ts", or a commit subject "fix: handle C:\new on windows" has no real newline, so decodeEscapes still fired and injected a real newline mid-string. Root cause: the rescue was gated by content (no-real-newline), not by field purpose. Titles / notes / verifications / commit subjects / artifact labels are never rendered as Markdown, so their '\n' is always backslash + n. Only the plan-level summary and the per-item summary are Markdown-rendered and need the double-escape rescue. Split decodeEscapes into: - decodeUnicode(s): \uXXXX only (unambiguous, no path/regex writes \u literally). Keeps the (?<!\\) lookbehind so \\u stays verbatim. - decodeMarkdown(s): decodeUnicode + \n/\r/\t rescue, still gated by the no-real-newline second line of defence and by (?<!\\) for \\n. Agent-write paths route per field class via decodeIfAgent (unicode-only, for titles/notes/verifications/commitSubjects/artifact-labels) or decodeMarkdownIfAgent (summary fields). Plan-level setSummary uses decodeMarkdown directly (always agent-authored, always Markdown). Core SPEC rewritten to describe the split; tests updated to pin title/note/verification/commitSubject/artifact.label as verbatim, and a new test covers a Windows-path title + regex note + grep verification + commitSubject + artifact label all surviving unchanged. * fix(pi-todos): preserve promote-born empty groups across replaceAll replaceAll carried only groups with done items from the old plan; a group with todos.length === 0 that the fresh plan didn't re-emit by title was silently dropped. The one way an empty group gets created is TodoStore.promote (parking a user ask for decomposition), so this broke the promotion contract: 'the user's ask shown as its own group survives even if the session dies right after promotion'. Carry each pre-existing empty group forward with its original id when the fresh plan doesn't name it. Tested with promote → replaceAll restructuring an unrelated group — the parked ask is still present with its original id and empty todos. Core SPEC's leftovers rule updated to call out promote-born empty groups explicitly. * fix(pi-todos, server): syntax-aware markdown decode + host-driven PR summary Two blocking issues the bot flagged against the recent decode narrowing and the auto-summary anchor: 1. The field-class split still mangled short Markdown strings whose '\n' had no real newline nearby. The bot's examples: - plan_summary 'Adjusted C:\new file' got a real newline injected between 'C:' and 'ew file' - verification '- unit → pass\n- lint → pass' stayed verbatim and rendered as one line with literal '\n' even though the planSummary tool spec treats verification as Markdown. Replace the content-based (no-real-newline) guard with a syntax-aware one that only decodes '\n' when it precedes a Markdown structural separator: - MARKDOWN_NL_PARAGRAPH: a doubled '\n\n' (paragraph break) decodes both chars in one match - MARKDOWN_NL_STRUCTURE: a standalone '\n' decodes only before bullet / ordered list / heading / blockquote / fenced block / thematic break - Everything else (short prose, Windows path, regex, code snippet) stays verbatim - '\\n' still protected via (?<!\\) - '\r' / '\t' not rescued (too rare as separators, too close to CRLF line endings and hard-tab indentation) - Markdown-rendered field class expanded to {plan.summary, item.summary, item.verification}; the previous version omitted verification despite the tool spec SPEC rewritten; test rewritten to assert the three scenarios the bot reported (Markdown structure decodes, Windows path survives, plain note stays verbatim). 2. The auto-summary anchor still mounted only for sessions represented by an open workspace tab. Closing the last chat/Plan tab to history unmounted the anchor, so a background-completed plan could still race pr.preview to an empty summary. Drive summary generation from the host side: previewPr now awaits generateTodoSummary(params) before renderPrBody reads plan.summary. generateTodoSummary is idempotent (returns existing summary, no-ops on incomplete plan, shares the in-flight promise), so a UI-triggered draft and a PR-triggered draft collapse into one generation. New test in autoSummary.test.ts covers previewPr seeding the summary on disk and prepending it to the body. * fix(server, web, contracts): normalize fallback plan summary + track rail add surface Two open non-blocking findings from the bot: 1. assist.toPlanSummary accepted any non-empty prose and persisted it verbatim, so a cheap-model response of "Done." or a dropped heading shipped through pr.preview / pr.open as a malformed PR body. Add ensurePrBodyShape(text): if the model emitted ## Summary / ## Changes / ## Testing in order, pass through; otherwise treat the whole output as the Summary body and append placeholders for the missing sections ("_no cross-cutting notes_" / "_not verified_"). splitBySections tracks each heading's start/end and extracts only the body under it, so a Summary + Testing output (skipping Changes) rebuilds in correct order instead of swallowing the trailing section into Summary. Two new assist tests cover "Done." and "Summary + Testing only"; the concurrent-draft autoSummary test updated to assert the wrapped body shape rather than the raw string. 2. plan.add from PlanRailPane defaulted surface to "chat", so plan_item_added analytics attributed every rail add to the removed chat strip and corrupted the migration's surface breakdown. Add "rail" to PlanAddSurface (server/analytics/events), to the todo.add wire surface enum (contracts/wsProtocol), and to useChatTodos.add's parameter. PlanRailPane now calls plan.add(title, "rail"). Analytics SPEC + productAnalytics.test updated so plan_item_added surfaces read [chat, page, rail, chat]. * docs(web, server): drop stale plan popup/strip wording and dead RFC links * refactor(web, pi-todos): remove dead plan-strip code and stale remove guidance * fix(server): keep fallback plan summary sections unique when Summary is missing * chore(web, pi-todos, server): trim rationale comments to near zero * docs: describe the plan as agent-maintained with user-added tasks * feat(web, server, contracts): user can remove not-yet-done TODO items from the rail Bring back a UI Remove affordance, scoped to the user's queue: pending and in_progress items get a hover-visible \u00d7 button in the TODO rail that calls plan.remove(id). Done items carry no button \u2014 completed steps are the plan's audit trail. Reviewing items are also excluded (same reason the host already guards). Agent-side todo_remove tool stays unchanged and bypasses this gate (needed for todo_promote cleanup). - packages/contracts: restore todo.remove wire protocol (channel + handler type). - packages/server: - restore removeTodo in todos.ts with a status==='done' guard that throws an explanatory error. Review-latch guard (isItemUnderActiveReview) layered on top for defense in depth. - restore host handler and dropItemBaseline (needed for the baseline cleanup side of removeTodo). - 3 new reviewFlow.test cases: pending+in_progress remove happy path, done-item rejection, unknown-id no-op. - apps/web: - useChatTodos.remove(id) with optimistic patch + rollback-on-error. - TodoList: new onRemove prop threaded TodoRows \u2192 GroupBlock / LooseList \u2192 TodoRow; TodoRow shows a group-hover \u00d7 button only when onRemove && status!=='done' && !reviewing. DoneGroup never receives onRemove, so done items can't accidentally expose it. - PlanRailPane passes plan.remove through. - SPECs updated: panels/SPEC.md describes the new affordance and the done guard; todos/SPEC.md documents the done-guard and the agent/UI path split. * fix(web): use bg-control-bg-selected for the Remove button hover The hover state used `bg-control-bg-pressed`, which is not a published semantic token. The strict `colors:check` gate rejected it (regression on both E2E no-agent and binary smoke jobs). Swap for `bg-control-bg-selected` \u2014 the one strong-state control-bg token that exists in the token set. * fix(web): useChatTodos.remove owns the failure toast; scope SPEC to the rail Two open non-blocking findings on the Remove affordance: 1. The click handler at TodoList.tsx:431 did `void onRemove(todo.id)`, which discarded a rejecting promise \u2014 a stale click (row became done between render and click) surfaced as an unhandled rejection without telling the user why removal failed. Move the toast ownership into `useChatTodos.remove` itself: on reject it rolls back the optimistic patch from authority (`reloadPlan`), toasts "Couldn't remove the step" via the store's error channel, and returns. The promise resolves either way, so `void plan.remove(id)` call sites stay safe. In-doc comment names the invariant. 2. panels/SPEC.md:965 described the Remove button as a Plan-page affordance, but `plan.remove` is only wired through `PlanRailPane`. Scope the paragraph to the TODO rail where it actually lives: moved it out of the Plan-page section into the rail section, named it "not-yet-done rail items", and spelled out that the center-tab Plan page is a review-trail surface and does NOT expose Remove. Queue manipulation lives on the rail. * fix(server, web): clear auto-review cycles on UI removal and label the remove button * docs: restore user-editable plan wording now that the rail can remove items --------- Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 23 days ago | |
chore(e2e): move Playwright configs into e2e/ (#1154) * chore(e2e): move Playwright configs into e2e/ Pass -c from the runners and test:workflows, and keep test-results and playwright-report at the repo root so CI artifact paths do not change. * ci: select artifact jobs for the moved binary and desktop configs | 5 days ago | |
feat(ask-user-question): redesign the questionnaire card — stable height, notes and Other in place, rich previews, Discuss, receipt (#1192) * fix(ask-user-question): make every questionnaire control reachable by keyboard in WebKit * feat(pi-ask-user-question): add question previews, opt-in file previews, discuss-first answers and one-option confirmations * feat(extensions): add per-extension settings and share the workspace file check through the session seam * refactor(resource-renderers): share the framed file preview and add compact chrome for embedded frames * feat(ask-user-question): drive file previews from the rich-preview setting and teach the Discuss and preview conventions * refactor(ask-user-question): stack question pages at a stable height with in-place note slots and a real Other field * feat(ask-user-question): add question previews, a fixed preview pane and a per-question Discuss mark to the card * feat(ask-user-question): replace the answered record with a foldable question → answer receipt * test(ask-user-question): cover stable paging, growing fields, previews, Discuss, the receipt and the rich-preview switch in Chromium and WebKit * docs(specs): note the plan page's preview scope and the WKWebView tab-stop gap * fix(ask-user-question): keep Discuss across reconnects, let the Other checkbox cancel it, and make previews keyboard-scrollable * test(ask-user-question): click Submit only once the page-change reveal has settled * feat(ask-user-question): Enter picks, Space adds a note, d discusses and the platform chord submits in the card * feat(pi-ask-user-question): match the card's keys in the terminal — Enter picks, Space notes, Next and Submit rows, Ctrl+Enter sends * docs(ask-user-question): describe the explicit-selection keyboard model in the specs and live spec * chore(locales): restore package.json and tsconfig.json that a catalog edit rewrote * docs(web): note that the platform modifier now comes from @thinkrail/ui/keyboard * Document extension settings in the contracts, settings, host, store, transport and chat specs, move them to protocol v104 after the GitHub star ask took v103, and narrow stored setting flags with a type guard instead of a cast. * Gate Discuss on a shared ASK_DISCUSS_PROTOCOL_VERSION (v104) so a v103 host, whose formatter would report an empty discuss as no answer, never shows the toggle. * ci(e2e): fail over a stalled apt mirror when installing WebKit's system libraries * fix(server): load extensions with the setting snapshot the loader records, so a change and revert mid-reload never leaves a stale tool schema current * ci(e2e): cap each WebKit dependency install attempt so a trickling apt mirror retries instead of exhausting the wait * ci(e2e): install WebKit's missing libraries without Playwright's font group, whose mirror download stalled past the wait * Move extension settings and Discuss to protocol v105 after background workspace creation took v104 * test(e2e): assert the plan-page answer by its lasting receipt, not the transient sent state the card can leave before WebKit sees it --------- Co-authored-by: OLavrik <Olya_lavrik@inbox.ru> Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 1 day ago | |
chore(e2e): move Playwright configs into e2e/ (#1154) * chore(e2e): move Playwright configs into e2e/ Pass -c from the runners and test:workflows, and keep test-results and playwright-report at the repo root so CI artifact paths do not change. * ci: select artifact jobs for the moved binary and desktop configs | 5 days ago | |
chore(e2e): move Playwright configs into e2e/ (#1154) * chore(e2e): move Playwright configs into e2e/ Pass -c from the runners and test:workflows, and keep test-results and playwright-report at the repo root so CI artifact paths do not change. * ci: select artifact jobs for the moved binary and desktop configs | 5 days ago | |
chore(e2e): move Playwright configs into e2e/ (#1154) * chore(e2e): move Playwright configs into e2e/ Pass -c from the runners and test:workflows, and keep test-results and playwright-report at the repo root so CI artifact paths do not change. * ci: select artifact jobs for the moved binary and desktop configs | 5 days ago | |
chore: near-zero code comments — rationale lives in specs (#269) Strip ~10.6k comments across apps/packages/e2e/scripts (parser-accurate, string/JSX-safe). What survives: biome-ignore directives, five one-line hazard notes pointing at their specs, and opaque-literal label tables (unicode controls, git ref syntax, DEC modes, chunk-error strings, line-number fixtures). Rationale not yet specced was promoted first: xterm #6065 IME rescue -> panels/SPEC.md; palette-reset ordering + alias self-reference -> apps/web/scripts/SPEC.md. Everything else checked was already recorded in the owning SPEC.md; the abandoned wsProtocol v4-v17 changelog is deleted (git history retains it). Codify the rule where agents read it: AGENTS.md ('Comments: avoid them. Near-zero is the norm.' + a 'No comment creep' handoff audit), writing-specs (specs are the only home for rationale), brainstorming step 9 (self-review checks for rationale comments). | 2 months ago | |
perf(resource-renderers): mount Monaco directly without @monaco-editor/react (#816) * perf(web): open file preview at once on single click * fix(web): hold the Review reveal while a browse row's double-click can still land * fix(web): reveal Review at once when it already shows The double-click hold only protects a browse row that the Review reveal could replace. When Review already shows, the held reveal only moved its focus request up to 250 ms late, closing whatever the user had opened in the meantime, such as a comment's delete confirmation. * fix(web): let a deferred Review reveal yield to a newer tool pick, and drop timing from its tests The Review reveal deferred for a browse row's double-click only re-checked the center selection, so a tool picked inside the window (Specs) was replaced. The reveal now snapshots the showing auxiliary tools (showingAuxiliaryKey) and skips when they changed. firstClickOnly and doubleClickPendingMs take an optional clock and scheduleReviewReveal takes the pending window, so their unit tests no longer sleep to the window boundary or inherit another test's click. preview-tabs.spec freezes the page clock instead of a 200 ms budget, plays the settle window out with clock.runFor instead of waitForTimeout(300), and waits for the released read's answer before asserting it was dropped. Specs: lib names the reveal as the third settle-window user, layout adds that the Changes tab waits for code-driven opens too, and e2e records the frozen-clock pattern. * fix(resource-renderers): jump to a review focus that arrives with the Monaco mount Opening a worktree comment now lands in the file pane. The editor's mount reveal used a smooth scroll, which can stop short before the editor's first layout; the focus is then marked handled and never retried, so the card stayed off screen. That made 'opening a far-off worktree comment scrolls its card into view in the file pane' fail about 1 run in 20, and 1 in 10 with tracing. The mount reveal now scrolls immediately; a focus on a mounted editor still scrolls smoothly. * perf(resource-renderers): load grammars per language on Oniguruma WASM * fix(resource-renderers): recover a failed grammar's provider and find nested fence embeds * fix(resource-renderers): load embeds referenced inside other embeds * fix(resource-renderers): retry a failed highlighter start instead of caching it * fix(resource-renderers): use the JS engine only without WebAssembly, and let a failed theme cost only colours Shiki's loadWasm keeps a rejected start for the page, so one failed onig.wasm fetch pinned the 4.5x slower JS engine until reload. The asset is now fetched and compiled here and Shiki only instantiates the module; a failed fetch or compile fails that start and the next file retries. The JS engine runs only where WebAssembly does not exist. The highlighter was stored before its theme was selected, so a theme that failed to load left every file plain and made each later request build another engine. Theme failures are now swallowed at start and retried by the next swap. Concurrent embed scans each report the embeds they finished that the provider lacks, so a second model no longer waits for the first model's slower chunk. Implicit HTML needs a tag at a line start or a closing tag, so a generic like Array<T> in a fence no longer pulls in html. Drops a comment that restated the spec. Tests: the fence e2e uses sh, which nothing else loads, so it exercises the embed scan; a new e2e aborts the first onig.wasm fetch and expects the next file to fetch it again; the light/dark swap test restores the default theme. * fix(resource-renderers): count inline HTML elements mid-line as an html embed Narrowing the implicit-HTML scan to line-start and closing tags missed inline void tags such as <img ...> or first<br>second, so their markup stayed plain. The scan also accepts a comment and a common inline element followed by whitespace, a slash or >, which still rejects generics like Array<T> and Map<K, V>. * perf(resource-renderers): preload the code view before the first file open * fix(web): preload the code view once per active page, warm rows only after a hover rests Hover and focus warmed each row's top-candidate renderer, so hovering a mixed tree loaded the PDF, notebook and CSV chunks. Only renderers with capabilities.preload (the code view) now load early, and a hover warms after the pointer rests 150 ms on a row; focus still warms at once. The idle preload ran on every workspace switch and in hidden or offline tabs, where a failed fetch makes Chromium cache the chunk's failure for the session. It now runs once per page and lib's whenPageActive holds it until the page is visible and online. A lazy that a mount stored while a load was in flight stayed cached after the load rejected, so the next mount reused the failure. The failed load now drops that lazy too. New unit tests pin that a stored lazy is never swapped for the direct component, that a failed in-flight lazy is retried, and that non-preloading renderers are left alone. The phone e2e no longer sleeps 2.5 s. It drives the idle schedule with page.clock, waits for a sentinel request before asserting that no editor chunk was requested, and then checks that the same path does preload at desktop width. Specs updated: web, panels, shell, lib, resource-renderers. * fix(web): recheck that the page is active right before the idle preload runs whenPageActive stopped watching once it scheduled the idle task, so a page that went offline while the idle callback, fallback timer or Files-tree gate was pending still imported the chunk, and Chromium kept the failed fetch for the session. It now takes the scheduler and the task, checks activity when the scheduled run fires, and waits for visibilitychange/online again if the page went hidden or offline. The preloaded-theme e2e restores the default theme like its siblings. * perf(resource-renderers): mount Monaco directly without @monaco-editor/react * fix(resource-renderers): skip the mount's redundant Monaco updates, and record what the direct mount costs The model and options effects re-applied on mount what createFileEditor had just set. Both now skip a first run whose inputs match the mount, so a fresh open no longer pays for a second updateOptions. The spec now gives one run's numbers (the PR's table) and the measured chunk change (3.10 MB to 3.09 MB, 12.1 kB, 3.5 kB gzip). It says why text is compared to the last applied string (the editor is read-only) and that a re-suspend inside the view's boundary would dispose the editor. e2e/SPEC.md no longer explains the hover rule with the removed wrapper. review.spec.ts wheels until it observes the card leave the viewport, instead of a toPass loop that waited 500 ms per 50 px tick. The editor.spec close/reopen test is renamed to what it checks. --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 13 days ago | |
feat(website): add consented journey analytics foundation (#524) * Update website analytics specs for settled mechanics * feat(website): add consented journey analytics foundation * fix(website): clear journey enrichment when consent becomes unknown * feat(website): instrument attribution events (#526) * feat(website): instrument attribution events * test(website): cover unattributed install events * feat(website): persist campaign attribution context (#528) * feat(website): persist campaign attribution context * fix(website): import attribution through its public surface * fix(website): invalidate download bridge on new acquisition * fix(website): keep attribution context through unknown consent * feat(website): add attribution claim protocol core (#529) * feat(website): add attribution claim protocol core * feat(website): deploy browser attribution claims (#530) * feat(website): deploy browser attribution claims * fix(website): require confirmation before binding attribution claims * feat(analytics): add first-install lifecycle (#531) * feat(analytics): add first-install lifecycle * fix(analytics): claim first installs once across concurrent hosts * feat(analytics): link browser acquisition to product usage (#532) * feat(analytics): link browser acquisition to product usage * fix(analytics): revoke queued campaign basics and allow claim confirmation * test(analytics): verify website-to-product attribution contract (#534) * refactor(attribution): drop landing page key from claim touches * fix(website): keep attribution D1 out of preview deployments * fix(website): derive analytics keys from public page paths * test(website): validate analytics across every generated page * fix(website): guard analytics and assets on all built pages * refactor(website): drop extra build checks and simplify the 404 page * feat(website): link claims silently and open the blog * fix(web): describe website attribution in plain language * fix(web): avoid promising deletion in the attribution note * fix(web): describe the blog opening as conditional * adjusts wording * docs: align privacy specs with the simplified settings copy * fix(website): bind claims only with current Marketing consent * docs(web): drop removed privacy copy from the panels spec | 1 month ago | |
chore(e2e): move Playwright configs into e2e/ (#1154) * chore(e2e): move Playwright configs into e2e/ Pass -c from the runners and test:workflows, and keep test-results and playwright-report at the repo root so CI artifact paths do not change. * ci: select artifact jobs for the moved binary and desktop configs | 5 days ago | |
[Design] Unify buttons into one system and move the app onto it (#817) * feat(ui): unify buttons into one system and move the app onto it packages/ui Button and IconButton become the single button system: primary, soft-primary, outline, ghost and destructive at S/M/L (20/24/32px; 14/16/20px icons), iconLeft/iconRight, square icon-only buttons whose label is also the shared tooltip, loading (icon-sized spinner, blocks re-triggering) and a persistent selected state. Labels use a new ui.button style (470 weight). Every state is a semantic token: new dangerHover/onDanger palette keys and control-destructive-* roles, a soft-primary hover role that keeps the accent label AA, an offset primary focus outline visible over every fill, ghost focus with its hover fill, pressed identical to hover, disabled never hidden. Ordinary action buttons across apps/web, resource renderers and the visualize extension move onto it (including the new i18n strings from main); panel tool rails wear the ghost M skin without the accent edge bar. Legacy variant/size names are removed and buttonUsage.test.ts rejects them, call-site skins and raw <button> re-skins. IconTooltip no longer opens on programmatic focus and closes before the layer beneath it on Escape. * docs(design-audit): add the button-system catalog and migration record buttons.html renders the production Button/IconButton in all six themes with every state, the app's selections and the real tool rails; verify-buttons.ts checks geometry, focus and contrast; buttons-migration.md records every old-to-new change and each remaining raw button with its reason. * fix(web): show the reason on a disabled remove-group button The empty-group remove IconButton wraps its tooltip trigger, so a disabled button still explains why it cannot remove the group. * docs(web): allow the prompt module's shared Button dependency in its specs * feat(ui): let a loading button spin its own refresh icon in place Button and IconButton gain spinIcon: with loading, the button rotates its own icon instead of swapping in the spinner. The five refresh controls the components loading vocabulary names (GitHub, providers, both model pickers, branch picker) use it, so their RefreshCw keeps spinning in place as specced. * fix(web): move Environment settings onto the shared button system * fix(ui): show tooltips on disabled buttons and replace title with a tooltip prop * fix(ui): raise ghost actions inside hover-filled rows to the elevated surface * fix(ui): give soft-primary a selected look and mark the expanded file chip * feat(ui): add a tone prop so warning and danger actions keep their colour in every state * fix(web): square the composer Stop button at narrow widths and give it a tooltip * refactor(ui): share button data attributes and icon wrappers, and move JSDoc into the spec * fix(ui): cover fieldset-disabled and reduced-motion loading, keep icon focus rings unclipped, lift tooltips above toasts * fix(web): restore rail drop hints and badges, the comment icon's elevation, short queue tooltips, and the Changes picker pair * docs: align the button specs and the migration log with the review fixes * fix(web): move the new Discord link onto the shared button system * docs(web): describe the current button API in the Projects and notification specs * fix(ui): wrap a tooltip trigger only while its button is disabled, and merge buttonVariants compounds * fix(web): keep history-row action hovers and Changes toolbar focus rings visible * fix(web): name the current diff scope on the Changes scope trigger for screen readers * docs(ui): note that IconButton defaults to size m * fix(web): move the custom themes folder actions onto the shared button system * fix(themes): derive dangerHover and onDanger for theme files written before they existed * fix(design-audit): initialize themes through the renamed initializeThemes export * fix(web): keep the Japanese catalog and the button guard in step with main's moved ask card --------- Co-authored-by: Julia.Shilova <Julia.Shilova@MacBook-Pro-5.local> Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 10 days ago | |
feat(web): first-run onboarding — intro, privacy and connect-and-verify wizard, recommendations from detected accounts, and Start a new project (#808) * squash: onboarding-first-run * feat(onboarding): harden account detection, share the consent draft between the dialog and the wizard's privacy step, let custom presets place the TODO List, and align the layout e2e specs with Balanced opening on it * fix(onboarding): advance the privacy step only after its consent save lands, verify a provider connected before the wizard reaches Connect, and record the new-project Default exception in the top-level docs * fix(onboarding): keep provider status in one store snapshot invalidated by every change, adopt a consent choice confirmed elsewhere, fence the new-project setup continuation, offer API keys on every provider, and make the onboarding e2e deterministic * test(projects): write the rollback test's hooksPath with forward slashes so git can parse it on Windows * fix(locales): drop the Spanish strings for the removed login Submit key and the reworded open-project subtitle * fix(locales): drop the Japanese strings for the removed login Submit key and the reworded open-project subtitle * Harden onboarding connect: auto-recheck empty model lists, cancel a pending login on skip, split the wizard into step components, drop the viewport-cap table, and emit a created project once * Verify a connection against an awaited model.refresh when the cached model.list is empty, instead of a fixed-delay re-read * fix(onboarding): re-read provider status on Welcome entry and on another login's success, and hand off only from a current snapshot * test(onboarding): settle the host before each cold start, run every teardown step, and drop wall-clock and inherited git-config dependence * fix(onboarding): take the connect dialog's baseline from its first report, so a sign-in that lands before the opening re-read still hands off * test(onboarding): read config.json tolerantly inside polls, remove the projects folder with retries, and count the toast before it auto-dismisses * test(shared): wait for the Central watcher's second re-arm instead of a fixed 5 ms sleep --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 10 days ago | |
perf(web): let the React Compiler optimize components that used try/finally (#800) * perf(web): let the React Compiler optimize components that used try/finally Move try/finally and throw-in-try bodies into async closures chained with .catch/.finally (or a module helper for the sync layout case), and hoist conditionals out of try/catch, so 15 components and hooks compile instead of bailing out. Record the rule in apps/web/SPEC.md, drop the fixed entries from its bailout list, and regenerate the compiler census baseline. * test(e2e): cover a failed provider sign-out after the try/finally rewrite The logout handler's early return now goes through a false result instead of a return inside catch, and only the NewWorkspaceDialog early return had e2e coverage. The new test fails the first provider.logout over the wire and checks the error toast, that the provider stays configured, and that Sign out is enabled again and works. The web spec now names every value block the compiler rejects inside try/catch. --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 13 days ago | |
feat(web): calmer Connect dialog — no raw OAuth URLs, smarter paste fallback (#761) * feat(web): hide raw OAuth URLs and collapse the paste fallback in the Connect dialog * feat(server): shorten the MCP sign-in instructions * test(e2e): cover the collapsed paste fallback and document the Connect dialog rules * fix(web): collapse the paste field only for loopback-redirect sign-ins * fix(web): make the sign-in opener a real link and offer manual copy when the clipboard fails * test(e2e): cover copy-code sign-in and clipboard failure in the Connect dialog * docs(web): raise the browser floor to Safari/WebKit 17 | 20 days ago | |
feat(web): borderless labelled composer controls (#628) * feat(web): borderless labelled composer controls * docs(web): record the composer trailing-controls decisions Spec the one-solid-object-and-ghosts cluster, the inert send fill exception, and the @md compact mode; update the streaming placeholder assertion in queue.live.spec.ts; drop the comment left on the composer's responsive constants. | 1 month ago | |
feat(web): first-run onboarding — intro, privacy and connect-and-verify wizard, recommendations from detected accounts, and Start a new project (#808) * squash: onboarding-first-run * feat(onboarding): harden account detection, share the consent draft between the dialog and the wizard's privacy step, let custom presets place the TODO List, and align the layout e2e specs with Balanced opening on it * fix(onboarding): advance the privacy step only after its consent save lands, verify a provider connected before the wizard reaches Connect, and record the new-project Default exception in the top-level docs * fix(onboarding): keep provider status in one store snapshot invalidated by every change, adopt a consent choice confirmed elsewhere, fence the new-project setup continuation, offer API keys on every provider, and make the onboarding e2e deterministic * test(projects): write the rollback test's hooksPath with forward slashes so git can parse it on Windows * fix(locales): drop the Spanish strings for the removed login Submit key and the reworded open-project subtitle * fix(locales): drop the Japanese strings for the removed login Submit key and the reworded open-project subtitle * Harden onboarding connect: auto-recheck empty model lists, cancel a pending login on skip, split the wizard into step components, drop the viewport-cap table, and emit a created project once * Verify a connection against an awaited model.refresh when the cached model.list is empty, instead of a fixed-delay re-read * fix(onboarding): re-read provider status on Welcome entry and on another login's success, and hand off only from a current snapshot * test(onboarding): settle the host before each cold start, run every teardown step, and drop wall-clock and inherited git-config dependence * fix(onboarding): take the connect dialog's baseline from its first report, so a sign-in that lands before the opening re-read still hands off * test(onboarding): read config.json tolerantly inside polls, remove the projects folder with retries, and count the toast before it auto-dismisses * test(shared): wait for the Central watcher's second re-arm instead of a fixed 5 ms sleep --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 10 days ago | |
perf(web): preload ChatView on workbench mount so a restored chat skips the Suspense reveal delay (#789) * perf(web): preload ChatView on workbench mount so a restored chat skips the Suspense reveal delay The lazy ChatView chunk was fetched only when the restored session arrived. That render comes from a store update and is synchronous, so React committed the Suspense fallback and then held the reveal for its ~300ms fallback throttle. A plain early import() does not help: lazy() still suspends once. preloadedLazy returns a component plus preload(). WorkspaceWorkbench calls preload() on mount, so the chunk loads alongside the chat data, and the component renders the loaded module directly, using React.lazy only while the chunk is in flight. Welcome and Project Home never fetch the chunk. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(e2e): a restored chat reveals without a Suspense skeleton once its transcript arrives Holds the restore read for 1s, then asserts no "Restoring chat" skeleton is inserted once the transcript lands. Fails on main, where the lazy ChatView chunk only starts loading at that point. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(e2e): wait for the ChatView chunk instead of a fixed 1s sleep The restored-chat test slept 1s and assumed the preloaded ChatView chunk had loaded by then; on a slow runner it would fail without a product regression. It now waits for the chunk response and its evaluation before releasing the held transcript. Also lists preloadedLazy in the shell -> components dependency edge. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 14 days ago | |
test(e2e): make real-Central agent runs deterministic (#352) * todo: Зафиксировать одобренный дизайн в task-spec и e2e SPEC ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_ea68a25a38b5 * todo: Реализовать изолированный real-Central agent harness ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_6c99016803f9 * todo: Добавить регрессионные проверки harness ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_05797da1abbb * todo: Прогнать локальные проверки и полные e2e ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_c293b80879f5 * todo: Провести финальный review diff и подготовить коммит ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_25b97956ddef * test(e2e): stabilize live runs after rebase | 2 months ago | |
feat(web): continuous Changes review tab with hunk triage, turn receipts and a review guide (#668) * docs(web): specify the Changes review tab presentation and its store/layout contracts * feat(web): add the changes review tab kind to the store and layout model * refactor(web): extract the shared DiffSurface from DiffPane * feat(web): add the continuous Changes review tab * test(web): cover the Changes review tab and adopt the click/double-click grammar in e2e * feat(web): track kept hunks on the changes review tab by content key * feat(web): add hunk keep triage and a progress bar to the changes review tab * test(web): cover hunk keep triage and document the overlay contract * feat(server): record per-run worktree snapshots as turn change sets with a turn diff scope * feat(web): add the Last turn diff scope and agent-turn list to Changes * feat(web): show the host's change receipt and a Review turn action under each agent round * test(web): cover turn receipts and the Last-turn scope end to end * feat(server): record the reviewer's summary and reading order as the review guide * feat(web): add the guided review rail to the changes review tab * test(web): cover the guided review walk and document the guide rail * fix(web): keep the Pierre highlight worker pool alive for the page * docs(web): record the persistent Pierre pool and review-section shape rules * fix(server): deliver turn receipts live and close the turn-tracker and review-guide races * fix(web): correct the review tab's receipts, send scope, shortcuts, triage keys and progress handling * test(web): reproduce triage progress loss on section remount * fix(review): preserve drafts and progress and close receipt and guide races * fix(server): keep a turn snapshot when git skips an unreadable file * fix(server): bound the shutdown wait for in-flight turn receipts * docs(server): record that a turn receipt spans the workspace interval * docs(web): name the Changes review tab's regions in the UI terminology * fix(web): isolate review-tab writes and harden reveal focus, scroll restore and progress styling * fix(web): open a turn's review tab without waiting for the Changes panel's status * refactor(web): give the review thread card one edit-state path on every surface * test(web): let review sections settle before the triage walk clicks Keep * fix(web): give each reading step its own guide cursor position * fix(web): let a review tab whose commit or turn is gone drop its list and offer all changes | 27 days ago | |
feat(web): group review settings into a Review section on the Models page (#744) | 22 days ago | |
fix(web): preserve per-file review chat routing (#1140) Co-authored-by: Rinat S <rinat.suleimanov@jetbrains.com> | 4 days ago | |
fix(e2e): keep macOS awake during browser runs (#389) | 2 months ago | |
chore(e2e): move Playwright configs into e2e/ (#1154) * chore(e2e): move Playwright configs into e2e/ Pass -c from the runners and test:workflows, and keep test-results and playwright-report at the repo root so CI artifact paths do not change. * ci: select artifact jobs for the moved binary and desktop configs | 5 days ago | |
chore(e2e): move Playwright configs into e2e/ (#1154) * chore(e2e): move Playwright configs into e2e/ Pass -c from the runners and test:workflows, and keep test-results and playwright-report at the repo root so CI artifact paths do not change. * ci: select artifact jobs for the moved binary and desktop configs | 5 days ago | |
chore(e2e): move Playwright configs into e2e/ (#1154) * chore(e2e): move Playwright configs into e2e/ Pass -c from the runners and test:workflows, and keep test-results and playwright-report at the repo root so CI artifact paths do not change. * ci: select artifact jobs for the moved binary and desktop configs | 5 days ago | |
chore(e2e): move Playwright configs into e2e/ (#1154) * chore(e2e): move Playwright configs into e2e/ Pass -c from the runners and test:workflows, and keep test-results and playwright-report at the repo root so CI artifact paths do not change. * ci: select artifact jobs for the moved binary and desktop configs | 5 days ago | |
feat: global search palette — find chats, workspaces and projects, run commands, open Settings sections (#804) * feat(contracts): add the shared tiered search matcher * feat(web): add the search palette in place of the session switcher `Mod+K`, double Shift and a topbar button open one palette that finds chats, workspaces and projects across every open project, runs commands with their shortcuts shown, deep-links Settings sections and toggles MCP servers and extensions in place. The empty query keeps the former switcher's role as the "Waiting for you" group next to Recent and Actions. Two new web sub-modules: `commands` (the typed contract an invocable action is described with; handlers stay with their owners) and `search` (props-driven palette, prefix/filter grammar, tiered ranking over the shared matcher with decay-free frecency and learned query→pick, client-local preferences, the double-Shift state machine). Shell composes the catalog, pages and row actions and injects them; `panels` import neither module. * docs(web): describe the search palette in the shell, store, notifications and module specs Also the README capability line for the palette. * test(e2e): cover the palette's MCP page toggle and the Tab/prefix rules in the search spec * fix(web): address the palette review — complete title backfill, offline terminal, IME, protocol gates, quoted filters - Chat-title backfill now covers every untitled workspace (active and context-project first) four requests at a time, re-asks a workspace at most once a minute and keeps the titles across opens, instead of stopping at the first 24 forever. - `terminal.new` is a host mutation (placement reserves on the host), so it is disabled offline. - The action panel's Enter ignores an IME composition like the main input does. - `settings.extensions` / `settings.mcp` follow the same protocol gates as their Settings sections, and the extension catalog / MCP list are requested only when the host serves them. - Quoted spans are tokenized before state filters, so `"is:running"` stays literal text. * fix(locales): drop the retired session-switcher keys from the bundled non-English catalogs * refactor(web): flatten the search palette to one finder level Drop the inline Theme, Extensions and MCP pages with their page stack, the per-row action panel, in-place toggles and the is:/has: filter grammar. Every setting row now opens its Settings section, the double-Shift row included; Mod+K inside the palette closes it; a command always runs. * fix(web): hydrate every open project's workspaces for the palette and keep the title backfill current The palette's backfill now loads the workspace list of every open project it has not seen — not only projects that already have session-state records — so a collapsed project's workspaces are searchable, and it re-reads its queue whenever the normalized snapshot gains or loses a workspace, so a palette opened during cold-start hydration catches up. * fix(web): label unnamed chats in the palette by their first prompt, as the retired switcher did * fix(web): address the palette design and code reviews — focus-proof keys, IME, bounded backfill and learning, literal quotes, Settle gate - Keys live on the dialog frame, not the input: a click on a header, the footer or the preview no longer leaves Escape, Mod+K and the arrows dead; Escape comes through Radix's `onEscapeKeyDown`, so a synthetic close-item Escape still closes and a nested layer keeps its own. - An active IME owns every key (`isComposing` or the 229 sentinel, also used by double Shift); plain Ctrl+R only is swallowed, so Ctrl+Shift+R reloads. - Title backfill: one `session.list` per workspace until its session set changes (a failed read included), workspaces whose chats are all titled are skipped, `workspace.list` skips diff stats, and both queues keep four requests in flight across palette openings (`backfillQueue`, unit-tested); the queues re-read on welcome, open-project and workspace changes. - Learning: adaptive counts are computed once per query and keyed on the text without its scope prefix; a query keeps its 32 most recent picks. - Matcher: a quoted term is a contiguous literal (no acronym or subsequence), an unterminated quote runs to the end, and case plus Latin accents fold without moving highlight offsets (İ, final sigma). - Waiting for you leaves running chats out and shows eight rows before an in-place "Show N more"; a notification click resets an already-open palette (`searchSession`). - Settle is disabled with the tree's reason while an agent works or a chat needs you (`Command.disabledReason`); the palette offers Start a new project and Open a path on the host, shows the native Settings shortcut, and says Connecting rather than Disconnected before the first welcome. - Sections are named groups; the chips and the Preview toggle use the shared Button (`aria-pressed`). - Tests: matcher, learning, sections, backfill queue, catalog gates and store session units; e2e for IME keys, focus outside the input and scope switching; double Shift is dispatched deterministically and the bottom-panel case waits for its known initial state; fixtures follow #869's input state. * fix(web): record backfilled chats in the history projection and refresh a project list that misses a reported workspace - The palette's title backfill no longer keeps a private title cache: `session.list` results go through `noteClosedChats` into the workspace's history projection (the same rows reconciliation writes), so the rename broadcast every client receives updates a chat this client never opened; a reply from an earlier connection is dropped and retried on the next schedule. - A project whose list exists but lacks a workspace the session snapshot reports (created by a peer while this client was away) is listed again, and the reply is merged with `addWorkspace` so rows that arrived meanwhile are never replaced. - `paletteBackfill.ts` holds the ordering, membership and install rules, covered with the real store: rename after backfill, missed membership, no clobbering, backfill order. * fix(web): re-list palette titles after a reconnect or a timeout, and leave Mod+Shift+I and Mod+Alt+I to the browser - A workspace's listing is keyed on the welcome generation too, so a reconnect lists it again; a timeout or a dropped connection no longer marks it listed, only a host-refused read does. - `Mod+I` toggles the preview only without Shift or Alt, so the DevTools chords pass through. - search/SPEC lists `@thinkrail/locales` among the allowed dependencies. * fix(web): keep backfilled titles current across renames in flight and reconnects, and merge listed workspaces in the host's order - A `session.list` reply is installed only if no rename landed while it was in flight (`sessionTitleRevision`, bumped on every `session_info_changed`) and the connection is the same; otherwise it is dropped and asked again. - After a reconnect, workspaces the palette listed before are listed once more, so a rename missed while away reaches their rows; the effect also re-reads when a workspace gains or loses a session. - The queue runs a key once more after it lands when it was asked for while in flight (a reply dropped across a reconnect no longer waits for the palette to reopen). - A refreshed project list keeps the host's creation order, holds on to rows that arrived meanwhile and never replaces a row the client already has. - `selectKnownChatTitle` lets an unnamed layout tab yield to the history row's title. - `createPaletteBackfill` owns the queues and listing keys with injected sources; deferred-reply tests cover a rename in flight, a reconnect, a stale reply and refusal vs timeout. * fix(web): re-ask a palette listing a rename overtook without waiting for another trigger The backfill now watches the store itself while the palette is open (`watch()`), keyed on the welcome generation, the open projects, each workspace's session count and `sessionTitleRevision`, so a listing dropped because a rename landed in flight is asked again on its own; the hook only mounts the watch. Tests drive the store instead of calling `schedule()` by hand, including a session joining an existing workspace. * fix(web): load a project's workspace list before the palette opens a chat in it A chat row can point at a workspace whose list was never loaded (the palette lists chats from the session snapshot), and picking it unmounts the palette, dropping its queued listings. Opening it now loads the project's list first when the row is missing, and still opens if that fails, so the location bar, the URL and the tree highlight see the workspace. * fix(web): open a palette pick only while it is still the latest navigation, loading the list without diff stats A chat whose workspace row is missing first loads its project's list, now without diff stats and merged in the host's order (the backfill's merge), and opens only if no later pick or navigation (workspace, Project Home or tab) happened while it loaded (`paletteNavigation.ts`, unit-tested with deferred lists). * fix(web): count only user navigation when a palette pick checks it was overtaken The overtaken check compared the active tab, which a route target from an earlier pick changes on its own when its chat finishes opening; it now compares the workspace's navigation tick, which only user navigation advances, so a legitimate pick is no longer dropped while a user tab switch still is. * fix(web): guard the palette's Work in project folder and project picks as later picks Work in project folder entered the Default workspace without a stillWanted guard, so a late workspace.list reply could pull the user back after they chose another workspace; it now passes the navigator's pick guard. A project pick also supersedes a chat pick still loading its project, so picking the Project Home already shown is not undone by an older pick landing. * fix(web): adapt the palette to main's typed tests and Russian catalog * feat: carry chat titles on session-state records and drop the palette's title backfill * fix(web): keep AltGr and Option characters, return focus on close, and drop picks a round trip overtook * refactor(web): build palette commands for their context, share the chord handlers, and drop dead code * fix(web): order Waiting by urgency, fold decomposed accents, prefer visible fields and keep odd learned keys * docs: describe the palette as it ships — commands coverage, matcher placement, shell and web edges * test(e2e): check the palette's focus return before a pick navigates | 5 days ago | |
chore(e2e): type-check the e2e tree in typecheck:root (#1064) Add e2e/tsconfig.json (root config with an e2e include) and run it as an extra tsc step of typecheck:root. Fix the stale and trivial errors it surfaced: the removed "endTurn" stop reason, untyped evaluate element parameters, unchecked index reads, ask/visualize tool arguments that must be JSON objects, and exact-optional shard options. reload-navigation.spec.ts stays excluded as the remaining to-do. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 7 days ago | |
feat(web): edge tool rails that mirror the pane model, Minimal themes, and remount-free panel toggles (#680) Projects, Specs, Files, Changes and Review move from per-pane tab strips to persistent left/right/bottom edge rails. The rail renders the layout's group model instead of hiding it: entries come in clusters, one per pane in document order, separated by a hairline; one pressed icon per open pane; removed tools sit dimmed in a trailing cluster. Drops follow the clusters (entry halves insert into that pane, a "New pane here" zone at every boundary creates one) and the pane header title drags its tool. Terminal groups keep named session tabs, and Project Home composes the same rail and pane header. Tools and terminals never share a pane (canJoinAuxiliaryGroup): enforced in every mutation, in normalization on load and across workspaces, in passive terminal placement and preset remaps. The mixed-order anchors (beforeToolByTabId) and terminal recall (lastTerminalByGroup) go with it. Menus name a pane by its contents, hide commands that can never apply to a tab, and the layout owns New terminal / New terminal pane below. Panels no longer remount on shape changes: structural-only panel-group keys, committed sizes re-applied imperatively, and topology-dependent minimums relaxed for the one membership-change commit (useTopologySettled). Themes: the standard Light/Dark and High Contrast palettes are unchanged; the quieter Minimal palettes ship as Minimal Light / Minimal Dark. A theme is a palette and role usage never varies by theme. The rails live in the split workbench renderer (workbenchRails.tsx and friends); the interface design lab that produced the direction stays out of the repository (design/ is gitignored). The spec-graph index also skips the scratch directories other assistants keep in a project (.claude, .superpowers, .gsd, .mockups, .playwright-mcp), whose plans and generated skills carry spec-shaped frontmatter. README screenshots are re-captured from this head (the real project in an isolated host: workbench light/dark and the Specs tool), with alt text that names the edge rails. | 28 days ago | |
feat: connect your own OpenAI-compatible model servers from Settings → Providers (#1084) * feat: add custom OpenAI-compatible model servers from Settings → Providers Co-authored-by: Anatoly Lazarev <lazarevtill@lazarev.cloud> * fix: harden custom server key reuse, capabilities and header moves; document custom servers * docs: move custom servers to protocol v101 in the contracts and panels specs * fix(server): take a custom server's id from its host when the name has no Latin letters * fix: keep a custom server's origin fixed and never list unsafe saved addresses * fix: never reuse a custom server id, and reload custom servers on Refresh * fix(auth): count custom servers saved after startup as connected providers --------- Co-authored-by: Anatoly Lazarev <lazarevtill@lazarev.cloud> | 7 days ago | |
chore(e2e): type-check the e2e tree in typecheck:root (#1064) Add e2e/tsconfig.json (root config with an e2e include) and run it as an extra tsc step of typecheck:root. Fix the stale and trivial errors it surfaced: the removed "endTurn" stop reason, untyped evaluate element parameters, unchecked index reads, ask/visualize tool arguments that must be JSON objects, and exact-optional shard options. reload-navigation.spec.ts stays excluded as the remaining to-do. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 7 days ago | |
feat(web): group review settings into a Review section on the Models page (#744) | 22 days ago | |
chore: near-zero code comments — rationale lives in specs (#269) Strip ~10.6k comments across apps/packages/e2e/scripts (parser-accurate, string/JSX-safe). What survives: biome-ignore directives, five one-line hazard notes pointing at their specs, and opaque-literal label tables (unicode controls, git ref syntax, DEC modes, chunk-error strings, line-number fixtures). Rationale not yet specced was promoted first: xterm #6065 IME rescue -> panels/SPEC.md; palette-reset ordering + alias self-reference -> apps/web/scripts/SPEC.md. Everything else checked was already recorded in the owning SPEC.md; the abandoned wsProtocol v4-v17 changelog is deleted (git history retains it). Codify the rule where agents read it: AGENTS.md ('Comments: avoid them. Near-zero is the norm.' + a 'No comment creep' handoff audit), writing-specs (specs are the only home for rationale), brainstorming step 9 (self-review checks for rationale comments). | 2 months ago | |
feat(web): list newest workspaces first in the projects panel (#725) Co-authored-by: Olga Lavrichenko <lavrikolyalavrik@gmail.com> | 24 days ago | |
ci: shard the browser e2e suites across runners (#642) * docs(e2e): specify cross-job sharding of the browser suites * feat(e2e): let the source runner run one CI job slice of the suite * test(e2e): split artifact suites per test so CI can shard them evenly * ci: shard the browser e2e suites across runners behind stable gate checks | 1 month ago | |
chore(e2e): type-check the e2e tree in typecheck:root (#1064) Add e2e/tsconfig.json (root config with an e2e include) and run it as an extra tsc step of typecheck:root. Fix the stale and trivial errors it surfaced: the removed "endTurn" stop reason, untyped evaluate element parameters, unchecked index reads, ask/visualize tool arguments that must be JSON objects, and exact-optional shard options. reload-navigation.spec.ts stays excluded as the remaining to-do. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 7 days ago | |
feat(web): edge tool rails that mirror the pane model, Minimal themes, and remount-free panel toggles (#680) Projects, Specs, Files, Changes and Review move from per-pane tab strips to persistent left/right/bottom edge rails. The rail renders the layout's group model instead of hiding it: entries come in clusters, one per pane in document order, separated by a hairline; one pressed icon per open pane; removed tools sit dimmed in a trailing cluster. Drops follow the clusters (entry halves insert into that pane, a "New pane here" zone at every boundary creates one) and the pane header title drags its tool. Terminal groups keep named session tabs, and Project Home composes the same rail and pane header. Tools and terminals never share a pane (canJoinAuxiliaryGroup): enforced in every mutation, in normalization on load and across workspaces, in passive terminal placement and preset remaps. The mixed-order anchors (beforeToolByTabId) and terminal recall (lastTerminalByGroup) go with it. Menus name a pane by its contents, hide commands that can never apply to a tab, and the layout owns New terminal / New terminal pane below. Panels no longer remount on shape changes: structural-only panel-group keys, committed sizes re-applied imperatively, and topology-dependent minimums relaxed for the one membership-change commit (useTopologySettled). Themes: the standard Light/Dark and High Contrast palettes are unchanged; the quieter Minimal palettes ship as Minimal Light / Minimal Dark. A theme is a palette and role usage never varies by theme. The rails live in the split workbench renderer (workbenchRails.tsx and friends); the interface design lab that produced the direction stays out of the repository (design/ is gitignored). The spec-graph index also skips the scratch directories other assistants keep in a project (.claude, .superpowers, .gsd, .mockups, .playwright-mcp), whose plans and generated skills carry spec-shaped frontmatter. README screenshots are re-captured from this head (the real project in an isolated host: workbench light/dark and the Specs tool), with alt text that names the edge rails. | 28 days ago | |
feat(server): re-attach compacted skill bodies after context compaction (#873) * feat(server): re-attach compacted skill bodies to every request after context compaction * test(e2e): check that the agent keeps following loaded skills across compactions * test(e2e): require that a restored skill needs no re-read after compaction so the live spec tells carry-over from recovery * fix(server): skip skill carry-over for virtual selections, budget the whole rendered message, keep partial reads from hiding complete loads, and resolve file URLs like pi's read tool * fix(server): restore a skill loaded before the compaction cut unless a complete load survives in the kept tail, so a paginated load re-read as an excerpt is no longer dropped * fix(server): count the lines each kept-tail skill load showed, as pi's read computes them, so a skill whose pages already show the whole file is not restored twice | 10 days ago | |
feat(web): restore bounded passive auto-open for live/unfinished chats (#361) Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 2 months ago | |
feat(web): edge tool rails that mirror the pane model, Minimal themes, and remount-free panel toggles (#680) Projects, Specs, Files, Changes and Review move from per-pane tab strips to persistent left/right/bottom edge rails. The rail renders the layout's group model instead of hiding it: entries come in clusters, one per pane in document order, separated by a hairline; one pressed icon per open pane; removed tools sit dimmed in a trailing cluster. Drops follow the clusters (entry halves insert into that pane, a "New pane here" zone at every boundary creates one) and the pane header title drags its tool. Terminal groups keep named session tabs, and Project Home composes the same rail and pane header. Tools and terminals never share a pane (canJoinAuxiliaryGroup): enforced in every mutation, in normalization on load and across workspaces, in passive terminal placement and preset remaps. The mixed-order anchors (beforeToolByTabId) and terminal recall (lastTerminalByGroup) go with it. Menus name a pane by its contents, hide commands that can never apply to a tab, and the layout owns New terminal / New terminal pane below. Panels no longer remount on shape changes: structural-only panel-group keys, committed sizes re-applied imperatively, and topology-dependent minimums relaxed for the one membership-change commit (useTopologySettled). Themes: the standard Light/Dark and High Contrast palettes are unchanged; the quieter Minimal palettes ship as Minimal Light / Minimal Dark. A theme is a palette and role usage never varies by theme. The rails live in the split workbench renderer (workbenchRails.tsx and friends); the interface design lab that produced the direction stays out of the repository (design/ is gitignored). The spec-graph index also skips the scratch directories other assistants keep in a project (.claude, .superpowers, .gsd, .mockups, .playwright-mcp), whose plans and generated skills carry spec-shaped frontmatter. README screenshots are re-captured from this head (the real project in an isolated host: workbench light/dark and the Specs tool), with alt text that names the edge rails. | 28 days ago | |
feat: pass each project's trust to pi and name what trusting it loads (#690) * feat: pass each project's trust to pi and name what trusting it loads - Project records carry pi-level trust (piResourceTrust), applied at every SettingsManager site including subagent children; existing projects are grandfathered unless pi's trust.json denies them. - project.setTrust({ resources: true }) is the only pi-level grant, sent by surfaces that name what it loads; trusted: false revokes both grants. - Grants and revokes reach live chats through the reload gate. - project.trustSummary (protocol v78) backs ProjectTrustNotice, which replaces ProjectSkillsNotice and gains Stop trusting. * fix(server): stop a stale parent's live subagents when project trust is revoked Children capture the project's pi-level trust at creation and pi-subagents retains them across a parent reload, so a revoke now aborts every live child of a session whose trust went stale, before that session's reload is requested. The Stop-trusting confirmation says so. * fix(server): never grandfather pi-level trust from an unreadable trust store piProjectTrustDecision distinguishes no saved decision (null) from a store it could not read (undefined); the migration leaves such a record without the field, which reads as untrusted, and seeds it on a later load. * fix(server): wait for revoked-trust subagents to stop before the trust change reports stopSessionChildren now exposes the settlement of every abort (a failed abort disposes the child); the trust revoke awaits it before requesting the parent reload, while Stop all keeps returning its count at once. * fix: refuse a child prepared across a trust change and report chats a trust change could not reload pi-delegation revalidates the projectTrusted binding after assembling a child and disposes one whose settings captured a trust that has since changed; applyPiResourceTrust reports a failed reload instead of swallowing it, and project.setTrust fails naming the chats still on the previous trust. * fix(server): tell the chat when a deferred resource reload fails after a settings change A trust change on a busy chat defers its reload; if that reload later fails, the chat now receives an error notify asking to close or reload it instead of silently keeping the previous resources. * fix(server): keep newly arrived alias skills pending when a grant adds pi-level trust * fix: derive a subagent's project trust from its parent's context * fix(server): reload a chat that finished opening under a stale project trust * fix(server): publish the project record when its trust changes * fix(server): stop grandfathering pi-level trust and seed new projects from pi's own decision * docs(server): state what a trust revoke stops and what finishes its turn * feat: name the kinds of pi resources a project's trust would load * feat(web): share the project trust notice with the Skills dialog and keep a revoke path * test: cover project extension trust and the Welcome and Skills dialog trust flows | 26 days ago | |
test(e2e): make real-Central agent runs deterministic (#352) * todo: Зафиксировать одобренный дизайн в task-spec и e2e SPEC ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_ea68a25a38b5 * todo: Реализовать изолированный real-Central agent harness ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_6c99016803f9 * todo: Добавить регрессионные проверки harness ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_05797da1abbb * todo: Прогнать локальные проверки и полные e2e ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_c293b80879f5 * todo: Провести финальный review diff и подготовить коммит ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_25b97956ddef * test(e2e): stabilize live runs after rebase | 2 months ago | |
feat(web): denser UI with named layout tokens and a tighter line-height scale (#703) * refactor(web): make the radius tokens Tailwind's radius scale and shorten call sites The --radius-xs/sm/md/lg tokens now live in an `@theme static` block that first resets Tailwind's default radius scale, so `rounded-<step>` resolves to the live tokens and undeclared steps such as `rounded-xl` are never generated: the 8px cap is enforced by the build. All 324 `rounded(-side)-[var(--radius-*)]` call sites become `rounded(-side)-*` across 123 files (mechanical; values unchanged). The radius guard rejects any arbitrary radius value, allows only declared steps (plus full/none) and pins the reset. apps/web/SPEC.md records the amended decision. * feat(web): denser UI with named layout tokens and a tighter line-height scale Cut the vertical and horizontal "lost space" in chat and the side panes, and move every value that must stay equal across surfaces onto a named layout role. - Layout roles (tokens.css, mapped in index.css): panel-row (22px side-pane rows), panel-inset (8px tool-pane padding), tree-indent (8px), chat-gutter (8px), panel-header-row (28px strips and the width of their full-height actions) and panel-header-control (header height - 4px). Rhythm roles alias spacing steps. - Line-height scale cut to 1.25 / 1.3 / 1.4 (prose and UI 18.2px); Monaco and the diff surface follow. Short exact decimals survive the minifier's 5-digit rounding. - Chat: no edge margins on message blocks, tighter step headers, copy action one line tall (h-lh); row-height estimates re-measured and resynced. - Tabs stretch to fill their strip; header controls fit their strips; cn (tailwind-merge) resolves role names against numeric steps. - Spacing guard accepts only declared rhythm roles; specs (SPACING.md, TYPOGRAPHY.md, chat/panels/layout SPECs) record the decisions and post-mortems. * feat(web): give reading prose its own 1.45 line height * feat(web): restore paragraph and row spacing in the chat transcript * fix(web): resync chat row-height estimates with the restored chat rhythm | 27 days ago | |
feat: configurable per-chat subagent concurrency (global + workspace) (#699) * feat(delegation): make the per-parent subagent limit live-resizable * feat(contracts): add the per-chat subagent concurrency setting and workspace override * feat(server): persist and live-apply the subagent concurrency limit per workspace * feat(web): add global and workspace subagent concurrency controls to chat settings * test(e2e): cover global and workspace subagent limits across clients * fix(web): let a cleared workspace subagent limit converge back to Use global * feat(web): show subagent settings as one Global | workspace table * test(e2e): close settings reliably after restoring subagent limits * fix(web): keep the global subagent limit editable while global delegation is off | 27 days ago | |
feat(web): collapse background results into one Resources-style row with its own round (#778) * fix(web): let background results and review fixes open their own chat round * feat(web): add the Resources result row grammar for the transcript * feat(web): render background results as one collapsed row that opens in Resources * docs(web): spec background result rows and round openers * fix(web): keep a hydrated round failure ahead of results that woke nobody * fix(web): close a round before results that woke nobody on live and reloaded chats | 18 days ago | |
feat(web): add sticky activity breadcrumbs (#318) * todo: Promote and approve the chosen design ThinkRail-Todo: 01a03f04-9a70-7c8d-a04e-dad7105ea6b8/t_f81e2ecc4e8f * todo: Implement sticky breadcrumbs test-first ThinkRail-Todo: 01a03f04-9a70-7c8d-a04e-dad7105ea6b8/t_2af3cd24f64f * todo: Verify and capture the breadcrumb UI ThinkRail-Todo: 01a03f04-9a70-7c8d-a04e-dad7105ea6b8/t_f6334bfef2d9 * fix(e2e): keep breadcrumb fixture model-neutral * fix(web): prevent long-chat crashes and resync compaction (#315) * todo: Draft and validate the recovery design ThinkRail-Todo: 01a03e67-0f3e-71f5-8113-71eac869713f/t_ba5a364c8500 * todo: Add a failing regression test ThinkRail-Todo: 01a03e67-0f3e-71f5-8113-71eac869713f/t_498c28840f48 * todo: Implement the root-cause fix and update specs ThinkRail-Todo: 01a03e67-0f3e-71f5-8113-71eac869713f/t_f78c9562ae5d * todo: Run full verification and review the diff ThinkRail-Todo: 01a03e67-0f3e-71f5-8113-71eac869713f/t_e4e4e40281d2 * fix(web): harden transcript resync * fix(web): prioritize reconnect transcript safety * feat(web): add an adaptive chat composer (#308) * todo: Review mockups together and capture the chosen design ThinkRail-Todo: 01a03d5d-7d37-72f8-aaf2-9dcfca683610/t_fe1105319f17 * todo: Add failing tests for the approved behavior ThinkRail-Todo: 01a03d5d-7d37-72f8-aaf2-9dcfca683610/t_6b613c5bdfe3 * todo: Implement the global composer growth setting ThinkRail-Todo: 01a03d5d-7d37-72f8-aaf2-9dcfca683610/t_c6084ad8e5ad * todo: Implement adaptive Composer layout and growth ThinkRail-Todo: 01a03d5d-7d37-72f8-aaf2-9dcfca683610/t_65918e2ad100 * fix(web): finish adaptive composer phone behavior * fix(deps): enforce one React runtime * fix(web): defer streaming transcript sync | 2 months ago | |
fix(server): reject template names containing a colon (#797) * fix(server): reject template names containing a colon * test(server): cover colon rejection in template names * Record the colon rejection in the template name gate * Reject colon in template names in the editor dialog too * Use the localized invalid-name error and drop the rationale comment * Mention the colon rejection in the invalid-name message * Drop the rationale comment from the name gate * Frame the colon rule as the NTFS alternate-data-stream case * style(templates): format the name gate, reflow its spec, and cover the colon in the editor e2e --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 13 days ago | |
test(e2e): make real-Central agent runs deterministic (#352) * todo: Зафиксировать одобренный дизайн в task-spec и e2e SPEC ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_ea68a25a38b5 * todo: Реализовать изолированный real-Central agent harness ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_6c99016803f9 * todo: Добавить регрессионные проверки harness ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_05797da1abbb * todo: Прогнать локальные проверки и полные e2e ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_c293b80879f5 * todo: Провести финальный review diff и подготовить коммит ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_25b97956ddef * test(e2e): stabilize live runs after rebase | 2 months ago | |
test(e2e): prove terminal output isolation with markers only command output can print (#838) | 14 days ago | |
chore(e2e): type-check the e2e tree in typecheck:root (#1064) Add e2e/tsconfig.json (root config with an e2e include) and run it as an extra tsc step of typecheck:root. Fix the stale and trivial errors it surfaced: the removed "endTurn" stop reason, untyped evaluate element parameters, unchecked index reads, ask/visualize tool arguments that must be JSON objects, and exact-optional shard options. reload-navigation.spec.ts stays excluded as the remaining to-do. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 7 days ago | |
feat(server): re-attach compacted skill bodies after context compaction (#873) * feat(server): re-attach compacted skill bodies to every request after context compaction * test(e2e): check that the agent keeps following loaded skills across compactions * test(e2e): require that a restored skill needs no re-read after compaction so the live spec tells carry-over from recovery * fix(server): skip skill carry-over for virtual selections, budget the whole rendered message, keep partial reads from hiding complete loads, and resolve file URLs like pi's read tool * fix(server): restore a skill loaded before the compaction cut unless a complete load survives in the kept tail, so a paginated load re-read as an excerpt is no longer dropped * fix(server): count the lines each kept-tail skill load showed, as pi's read computes them, so a skill whose pages already show the whole file is not restored twice | 10 days ago | |
feat(web): move the plan to a TODO List side rail, add todo_promote, and render the plan summary as a PR body (#706) * feat(pi-todos): add todo_promote for atomic loose-item promotion, with skill and spec updates * docs(pi-todos): shape the plan summary as PR body material and source shipping-a-pr from it * feat(web): Plan quick wins — inline agent message and done summary, plain-language sections and review labels, planning… line for empty groups * feat(web): move Plan to a right side-rail tool showing the compact TODO list, migrating old center-tab Plan documents * feat(web): make the TODO List rail the only plan surface — drop chat popover, header chip, and UI Remove; mute done items * fix(server): no baseline means no attribution — stop misclaiming dirty paths on bundled-done * fix(web): render the Plan summary as bounded Markdown, decoding literal escapes in agent-authored text * docs: draft RFCs for Plan v2, Plan side rail, chatless work surface, and touched-paths attribution * fix(web): fan out useChatTodos writes across rail and page via a refetch bus The rail (PlanRailPane) and the center Plan page each call useChatTodos and hold their own React state. Only agent-driven events (tool_execution_end / agent_settled) refetched both; a user-initiated add from the rail only patched its own state and the Plan page lagged until the next agent tick — timing-flaky on web no-agent, deterministically broken on binary/desktop (chat-plan.spec.ts:31 timed out 5/5 locally). Introduce a module-local pub/sub (todoRefetchBus) keyed on tupleKey("chat-todos", workspaceId, sessionId). Each hook instance subscribes on mount and reloads through the same 250ms debounce queue that already serves WS events. add / startReview / reviewAll signal the bus after their own optimistic patch, so sibling surfaces for the same chat stay in lockstep without touching the Zustand store or the transport contract. Snapshot the Set before dispatch so a React render-time subscribe that races a sibling write doesn't accidentally run for the in-flight signal (JS Set iteration otherwise picks it up). Also fix test ergonomics around the new tool-rail-plan: - Collapse the double tool-rail-plan.click() (a toggler closed the pane that had just opened) across plan-review / plan-ask / plan-open-pr / chat-plan into a single revealWorkbenchTool(page, "plan") call; extend the fixture's union with "plan" and leave a comment on why a bare click is a trap. - Update tool-rails right-rail cluster snapshots to include the new tool-rail-plan entry (parked as a trailing unplaced cluster by default, joined with the orphaned tool-rail-review after the remove-pane step). New regressions: - tool-rails: plan rail entry is an idempotent toggler + plan rail reachable by keyboard with proper aria-controls / aria-labelledby. - chat-plan: a task added from the Plan page shows up in the rail without an agent tick, an empty chat shows the empty-rail copy and no todo rows, two chats keep their own TODO lists (rail-write in chat A does not bleed into chat B, retarget on tab switch is symmetric). - useChatTodos.test: fan-out and key isolation, listener added mid-signal does not run for the signal in flight, duplicate subscribe fires once, idempotent unsubscribe, one listener's throw leaves the bus usable. * fix(pi-todos, web): address review findings on the plan rail — todo_promote collisions, todo_remove ghost groups, pending-ask glance Three findings from jetbrains-air on the PlanRailPane / pi-todos work. packages/pi-todos/core/store.ts - todo_promote (BLOCKING): now validates the normalized title BEFORE splicing the loose item. Reject an empty / whitespace-only override (a blank group has no addressable task for 'todo_add group:') and reject a collision with an existing group title (silent merge adds no step, so the second ask would vanish). The loose item stays put in both reject paths so the caller can retry with a disambiguating override. JSDoc updated to spell out the three reject cases. - todo_remove: prune a group whose last item was removed in this call, so an 'everything cleared' group doesn't leave a permanent ghost 'planning…' task that the user has no tool to delete. A promote-born empty group that was ALREADY empty before the call is preserved (the parked-user-ask invariant). Implemented by snapshotting pre-call todo counts and pruning iff count went from >0 to 0. apps/web/src/panels/PlanRailPane.tsx - Derive glance through hostSessionGlance(runtime.hostState, planGlance(…)) instead of planGlance(isStreaming, {}). The rail is now the only compact plan surface (the chat popover is gone), so when ask_user_question is pending the user needs to see 'Needs your answer' instead of 'paused'. Same path the center Plan page uses. - Expose data-glance on the pane's root so tests can lock the glance without an in_progress row mounted (StatusIcon only renders data-glance for in-progress items). Tests - pi-todos: four new unit cases — promote rejects collision (loose stays) + promote rejects empty/whitespace override + remove prunes a drained group + remove preserves a promote-born already-empty group + sanity remove-one-of-many. The previous 'promote merges into an existing group' test fixed the exact behaviour the blocking finding named as a bug, so it's replaced. - e2e: new 'a pending ask surfaces on the rail as waiting_question glance, not paused' in plan-ask.spec.ts, seeded with an awaiting ask_user_question toolResult. * fix(pi-todos, server): second-round review — spec the new promote and remove invariants, PR-body-shape the fallback plan summary Addresses the second review round on PR #706. packages/pi-todos/core/SPEC.md (BLOCKING) The authoritative core contract still said that a same-title promotion merges, which stopped being true in the previous round. Replaced the 'merges into an existing group' sentence with the full four-case reject list that the implementation now enforces (unknown id, grouped step, empty normalized title, title collision), spelled out that the loose item is left in place in every reject so the caller can retry with a disambiguating override. Extended the 'empty groups are first-class' section to cover the new 'remove-prune' half of the invariant — a promote-born empty group is preserved, a group that LOSES its last item in remove is pruned — so a reader finds the whole rule in one spot and no longer has to reconcile code and spec. packages/pi-todos/core/store.ts Refreshed the write() comment about empty groups so a future reader doesn't conclude from it that empty groups are kept unconditionally; pointed at remove() for the ghost-group half. packages/server/src/assist/assist.ts The fallback generateTodoSummary path previously asked for prose with 'no headings, no Summary: label, under ~120 words'. Since this round, every stored plan summary is treated as a PR body: the host persists whatever the fallback emits and renderPrBody prepends it verbatim. Reshaped PLAN_SUMMARY_SYSTEM to require the same three H2 sections (## Summary / ## Changes / ## Testing) with soft fallbacks for empty sections ('_no cross-cutting notes_' / '_not verified_'). Left toPlanSummary's code-fence / 'Summary:' normalization in place, with a note that H2 headings are explicitly kept. Tests - packages/server/src/assist/assist.test.ts: new fixture emits the three-section PR body and the test asserts both the returned text AND that the system prompt steers the model into that shape. - packages/server/src/todos/autoSummary.test.ts: same shape locked through the host-side persistence path. - packages/pi-todos/tools/tools.test.ts: the add→list→update→remove round-trip no longer expects a lingering empty group; the promote-born case stays covered in core/core.test.ts. * fix(web): render the em-dash in the empty TODO rail copy, not the raw '\u2014' escape A JSX text node does NOT decode backslash escapes, so '\u2014' between the words was shown to the user literally as the seven-character sequence. Use the real em-dash — in the source instead. * fix(pi-todos): collision-safe promote with full SPEC coverage Reject promotion of a loose item whose normalized title collides with an existing group (silent merge would make the second ask vanish); let the caller supply a unique title override; honor an explicit empty-title override as blank; align SPEC with the actual return shape. * fix(web): drop PlanRailPane fallback to planGlance(isStreaming, {}) Use sessionGlance(runtime) so the rail reads 'Needs your answer' from a pending ask_user_question surfaced only via the transcript, instead of showing the misleading paused/working glyph while hostState has not arrived yet. * fix(web): keep auto-summary trigger alive without rail/page mounted ChatView re-mounts useChatTodos (return discarded) so todo.generateSummary still fires on a completed plan whose rail is closed. Without this, renderPrBody prepended nothing when the user never opened the Plan surface. * fix(web, server, pi-todos): PR #706 review round 3 — rail analytics, bus hygiene, SPEC cleanup - rename PlanOpenSurface popup -> rail end-to-end (contracts, server analytics, useChatTodos, productAnalytics.test); PlanRailPane fires plan.notifyOpened("rail") once per (workspaceId, sessionId) mount so the signal the removed chat chip used to carry survives - ChatView re-mounts useChatTodos (discarded return) so the auto-summary trigger (todo.generateSummary on a completed plan with no summary) fires whenever a chat is open, not only while the rail or center-tab Plan happens to be mounted - signalTodoRefetch(key, except?) skips the originating hook via a selfListenerRef; dispatch is now try/catch-isolated per listener so a throwing sibling cannot swallow the signal, and the sibling-isolation test locks the contract - selectWorkspaceChatTab shared helper backs selectHistoryTarget + selectRailPlanSessionId - SPEC + UI-TERMINOLOGY purge of stale popover/strip references and Russian-quoted user-feedback asides; tabIcon pairs RiTaskLine/Fill for the Plan rail tool; placeholder reads 'Queue a task for the agent' - sanitizeGroup drops the double decodeEscapes that corrupted user-authored path-like titles * chore(server, web, pi-todos): drop todo.remove wire + stale RFCs, trim rationale comments - Delete `todo.remove` end to end: wire protocol, host handler, server `removeTodo` function, 3 reviewFlow tests, 1 artifacts.test race test. The UI dropped its delete affordance in the TODO-rail migration; the agent manages removal through pi-todos' own `todo_remove` tool, which goes through the pi-todos core store and never touched the server wrapper. `isItemUnderActiveReview` kept (used by other review assertions); `clearAutoCycles` dropped as dead too. - Drop stale RFC drafts that leaked into git: apps/web/src/panels/plan-{v2,as-side-rail,chatless-work-surface}-rfc.md and packages/server/src/todos/attribution-v2-rfc.md. - Trim long rationale comments on this branch down to one-liners that name what not why; the full why already lives in panels/SPEC.md, todos/SPEC.md and host/SPEC.md. Covers useChatTodos (bus docstring, dispatch, selfListenerRef, openPlan, sibling-fanout), PlanRailPane (header + glance + bg), ChatView (useChatTodos mount), selectors (shared chat-tab helper), promote (FAILURE_MESSAGES), artifacts (no-baseline branch). * fix(pi-todos): narrow decodeEscapes to write-time and skip strings with real newlines The previous rescue ran on EVERY read and expanded any literal \n/\r/\t in agent-authored text unconditionally. That silently mutated stored text that genuinely meant backslash-n: fenced code blocks, Windows paths, regex patterns, summary prose mentioning the \n character. On-disk data changed on reload without anyone writing. Narrow it to the one case it was invented for \u2014 a double-escaped summary from a model that forgot to inject real newlines: - Decode ONLY at the write boundary (add/update/promote/replaceAll/setSummary). Reads are strict pass-through: sanitize/sanitizeArtifacts no longer touch text, and old on-disk escapes stay verbatim on reload. - Decode ONLY when the string carries no real \n/\r/\t at all. A real newline is strong evidence the escapes were intentional. - Protect \\n via a negative lookbehind so an explicit "escaped backslash + n" survives even in the no-real-newline branch. - origin gate unchanged: user input is still verbatim. Spec rewritten to describe the new contract. Tests cover a rich summary with real newlines + inline \n mention, a Windows-path style note, a regex pattern, an escaped backslash + n, and a pass-through-on-read reload. * refactor(web, pi-todos, server): review-driven cleanup pass on branch Addresses the eight actionable findings from the branch self-review. Comments / tests / typing (3 items in one): - Replace literal `\u2014 / \u2026 / \u2192 / \u21d2 / \u2194` escape sequences with the actual Unicode characters in `//` and `/* ... */` comments across 8 files (16 lines). JS strings are left alone — they parse at runtime. - Rename the "self-heals on next write" test in pi-todos core to describe the actual (narrow-write-only) invariant: a legacy on-disk literal \\uXXXX is NOT decoded on read and the next write preserves it verbatim. - Type `ESCAPE_MAP` as `Record<"n"|"r"|"t", string>` and drop the unreachable `?? _m` fallback in the replace callback. - Restore the em-dash in a JSX `{/* ... */}` comment in `PlanPane` that had a stray LF. Dead-export deletion: - Drop `dropItemBaseline` from `baselines.ts` — only its own test still referenced it after the `todo.remove` wire removal. The test collapses to "removeSessionBaselines drops the whole sidecar and is idempotent". - `isItemUnderActiveReview` kept: production call sites are gone, but `planReview.test.ts` + `todoReview.test.ts` legitimately use it as the invariant probe for the fix-latch + planReviewQueue state. useChatTodos API shape: - Extract `broadcastWrite(key)` inside `useChatTodos` as the single home for the self-skip write-fanout; three sites (`add`, `startReview`, `reviewAll`) now call it instead of repeating `signalTodoRefetch(..., selfListenerRef.current ?? undefined)` verbatim. - Extract `useAutoPlanSummary(workspaceId, sessionId)` as a dedicated side-effect-only hook that keeps the auto-draft of the plan summary alive even when neither the TODO rail nor the Plan page is mounted. Subscribes directly to `WS_CHANNELS.piEvent`, fetches `todo.list` on `tool_execution_end` / `agent_settled`, and asks the host for a draft via `todo.generateSummary`; signals the refetch bus on success. - `ChatView` drops its ghost `useChatTodos` mount in favour of the new hook. No more full `todo.list` fetch + three WS subs just to arm one effect. - `PlanRailPane` drops its `eslint-disable-next-line react-hooks/exhaustive-deps`: the one-shot `plan_opened { surface: "rail" }` fire now hits `getTransport().request("todo.list", {..., opened: "rail"})` directly in the mount effect, so deps are exactly `[workspaceId, sessionId]` and no suppression is needed. React Compiler now compiles the component; baseline updated. - `notifyOpened` dropped from the public `ChatTodos` interface — no external consumer survives; the one internal use in `openPlan` was inlined as a raw transport call. * fix(web): keep plan auto-summary alive while any center tab is selected ChatView unmounts when the user switches to the Plan page or any other center tab, so mounting useAutoPlanSummary from ChatView meant the trigger silently died whenever the user was reviewing the Plan at the moment the agent finished the last step. The user would then click Open PR and renderPrBody read an empty plan.summary — the promised ## Summary / ## Changes / ## Testing body was missing. Move the mount to shell/WorkspaceWorkbench, which stays alive as long as the workspace is active. A tiny PlanSummaryAutoDraft anchor renders one useAutoPlanSummary per unique chat sessionId in the workspace's open tabs, so the trigger survives any center-tab switch for as long as the chat tab exists. Dedup across future tab kinds that reference the same sessionId falls out of the Set collection. Dropped the ChatView useAutoPlanSummary mount; updated chat/SPEC.md to describe the new owner. * fix(web): cover every session-backed tab in the plan auto-summary anchor Previous fix anchored the auto-summary trigger to `kind: "chat"` tabs only. A user who closes the chat tab to history but keeps the Plan page open (plan `doc` or the live center-tab `PlanTab`) would drop out of `chatSessionIds`, so the anchor unmounts; the agent could finish the remaining steps while the Plan page stays open and Open PR would still receive an empty plan summary. Export the existing `editorSessionId(tab)` helper from the store and use it in `WorkspaceWorkbench` to collect every unique sessionId across all session-backed tab kinds (chat / plan / doc). A new session-backed tab kind can't drift out of coverage without touching `editorSessionId` itself. * fix(pi-todos): scope markdown escape rescue to summary fields only, not every text field Reviewer bot flagged that the previous no-real-newline guard still corrupts short strings that genuinely contain backslash + n: an agent recording a title of "fix C:\new file.ts", a note like "ran /^\n/.test(x)", a verification "grep '\n' src/foo.ts", or a commit subject "fix: handle C:\new on windows" has no real newline, so decodeEscapes still fired and injected a real newline mid-string. Root cause: the rescue was gated by content (no-real-newline), not by field purpose. Titles / notes / verifications / commit subjects / artifact labels are never rendered as Markdown, so their '\n' is always backslash + n. Only the plan-level summary and the per-item summary are Markdown-rendered and need the double-escape rescue. Split decodeEscapes into: - decodeUnicode(s): \uXXXX only (unambiguous, no path/regex writes \u literally). Keeps the (?<!\\) lookbehind so \\u stays verbatim. - decodeMarkdown(s): decodeUnicode + \n/\r/\t rescue, still gated by the no-real-newline second line of defence and by (?<!\\) for \\n. Agent-write paths route per field class via decodeIfAgent (unicode-only, for titles/notes/verifications/commitSubjects/artifact-labels) or decodeMarkdownIfAgent (summary fields). Plan-level setSummary uses decodeMarkdown directly (always agent-authored, always Markdown). Core SPEC rewritten to describe the split; tests updated to pin title/note/verification/commitSubject/artifact.label as verbatim, and a new test covers a Windows-path title + regex note + grep verification + commitSubject + artifact label all surviving unchanged. * fix(pi-todos): preserve promote-born empty groups across replaceAll replaceAll carried only groups with done items from the old plan; a group with todos.length === 0 that the fresh plan didn't re-emit by title was silently dropped. The one way an empty group gets created is TodoStore.promote (parking a user ask for decomposition), so this broke the promotion contract: 'the user's ask shown as its own group survives even if the session dies right after promotion'. Carry each pre-existing empty group forward with its original id when the fresh plan doesn't name it. Tested with promote → replaceAll restructuring an unrelated group — the parked ask is still present with its original id and empty todos. Core SPEC's leftovers rule updated to call out promote-born empty groups explicitly. * fix(pi-todos, server): syntax-aware markdown decode + host-driven PR summary Two blocking issues the bot flagged against the recent decode narrowing and the auto-summary anchor: 1. The field-class split still mangled short Markdown strings whose '\n' had no real newline nearby. The bot's examples: - plan_summary 'Adjusted C:\new file' got a real newline injected between 'C:' and 'ew file' - verification '- unit → pass\n- lint → pass' stayed verbatim and rendered as one line with literal '\n' even though the planSummary tool spec treats verification as Markdown. Replace the content-based (no-real-newline) guard with a syntax-aware one that only decodes '\n' when it precedes a Markdown structural separator: - MARKDOWN_NL_PARAGRAPH: a doubled '\n\n' (paragraph break) decodes both chars in one match - MARKDOWN_NL_STRUCTURE: a standalone '\n' decodes only before bullet / ordered list / heading / blockquote / fenced block / thematic break - Everything else (short prose, Windows path, regex, code snippet) stays verbatim - '\\n' still protected via (?<!\\) - '\r' / '\t' not rescued (too rare as separators, too close to CRLF line endings and hard-tab indentation) - Markdown-rendered field class expanded to {plan.summary, item.summary, item.verification}; the previous version omitted verification despite the tool spec SPEC rewritten; test rewritten to assert the three scenarios the bot reported (Markdown structure decodes, Windows path survives, plain note stays verbatim). 2. The auto-summary anchor still mounted only for sessions represented by an open workspace tab. Closing the last chat/Plan tab to history unmounted the anchor, so a background-completed plan could still race pr.preview to an empty summary. Drive summary generation from the host side: previewPr now awaits generateTodoSummary(params) before renderPrBody reads plan.summary. generateTodoSummary is idempotent (returns existing summary, no-ops on incomplete plan, shares the in-flight promise), so a UI-triggered draft and a PR-triggered draft collapse into one generation. New test in autoSummary.test.ts covers previewPr seeding the summary on disk and prepending it to the body. * fix(server, web, contracts): normalize fallback plan summary + track rail add surface Two open non-blocking findings from the bot: 1. assist.toPlanSummary accepted any non-empty prose and persisted it verbatim, so a cheap-model response of "Done." or a dropped heading shipped through pr.preview / pr.open as a malformed PR body. Add ensurePrBodyShape(text): if the model emitted ## Summary / ## Changes / ## Testing in order, pass through; otherwise treat the whole output as the Summary body and append placeholders for the missing sections ("_no cross-cutting notes_" / "_not verified_"). splitBySections tracks each heading's start/end and extracts only the body under it, so a Summary + Testing output (skipping Changes) rebuilds in correct order instead of swallowing the trailing section into Summary. Two new assist tests cover "Done." and "Summary + Testing only"; the concurrent-draft autoSummary test updated to assert the wrapped body shape rather than the raw string. 2. plan.add from PlanRailPane defaulted surface to "chat", so plan_item_added analytics attributed every rail add to the removed chat strip and corrupted the migration's surface breakdown. Add "rail" to PlanAddSurface (server/analytics/events), to the todo.add wire surface enum (contracts/wsProtocol), and to useChatTodos.add's parameter. PlanRailPane now calls plan.add(title, "rail"). Analytics SPEC + productAnalytics.test updated so plan_item_added surfaces read [chat, page, rail, chat]. * docs(web, server): drop stale plan popup/strip wording and dead RFC links * refactor(web, pi-todos): remove dead plan-strip code and stale remove guidance * fix(server): keep fallback plan summary sections unique when Summary is missing * chore(web, pi-todos, server): trim rationale comments to near zero * docs: describe the plan as agent-maintained with user-added tasks * feat(web, server, contracts): user can remove not-yet-done TODO items from the rail Bring back a UI Remove affordance, scoped to the user's queue: pending and in_progress items get a hover-visible \u00d7 button in the TODO rail that calls plan.remove(id). Done items carry no button \u2014 completed steps are the plan's audit trail. Reviewing items are also excluded (same reason the host already guards). Agent-side todo_remove tool stays unchanged and bypasses this gate (needed for todo_promote cleanup). - packages/contracts: restore todo.remove wire protocol (channel + handler type). - packages/server: - restore removeTodo in todos.ts with a status==='done' guard that throws an explanatory error. Review-latch guard (isItemUnderActiveReview) layered on top for defense in depth. - restore host handler and dropItemBaseline (needed for the baseline cleanup side of removeTodo). - 3 new reviewFlow.test cases: pending+in_progress remove happy path, done-item rejection, unknown-id no-op. - apps/web: - useChatTodos.remove(id) with optimistic patch + rollback-on-error. - TodoList: new onRemove prop threaded TodoRows \u2192 GroupBlock / LooseList \u2192 TodoRow; TodoRow shows a group-hover \u00d7 button only when onRemove && status!=='done' && !reviewing. DoneGroup never receives onRemove, so done items can't accidentally expose it. - PlanRailPane passes plan.remove through. - SPECs updated: panels/SPEC.md describes the new affordance and the done guard; todos/SPEC.md documents the done-guard and the agent/UI path split. * fix(web): use bg-control-bg-selected for the Remove button hover The hover state used `bg-control-bg-pressed`, which is not a published semantic token. The strict `colors:check` gate rejected it (regression on both E2E no-agent and binary smoke jobs). Swap for `bg-control-bg-selected` \u2014 the one strong-state control-bg token that exists in the token set. * fix(web): useChatTodos.remove owns the failure toast; scope SPEC to the rail Two open non-blocking findings on the Remove affordance: 1. The click handler at TodoList.tsx:431 did `void onRemove(todo.id)`, which discarded a rejecting promise \u2014 a stale click (row became done between render and click) surfaced as an unhandled rejection without telling the user why removal failed. Move the toast ownership into `useChatTodos.remove` itself: on reject it rolls back the optimistic patch from authority (`reloadPlan`), toasts "Couldn't remove the step" via the store's error channel, and returns. The promise resolves either way, so `void plan.remove(id)` call sites stay safe. In-doc comment names the invariant. 2. panels/SPEC.md:965 described the Remove button as a Plan-page affordance, but `plan.remove` is only wired through `PlanRailPane`. Scope the paragraph to the TODO rail where it actually lives: moved it out of the Plan-page section into the rail section, named it "not-yet-done rail items", and spelled out that the center-tab Plan page is a review-trail surface and does NOT expose Remove. Queue manipulation lives on the rail. * fix(server, web): clear auto-review cycles on UI removal and label the remove button * docs: restore user-editable plan wording now that the rail can remove items --------- Co-authored-by: rsolmano <rinat.suleimanov@jetbrains.com> | 23 days ago | |
test(e2e): make real-Central agent runs deterministic (#352) * todo: Зафиксировать одобренный дизайн в task-spec и e2e SPEC ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_ea68a25a38b5 * todo: Реализовать изолированный real-Central agent harness ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_6c99016803f9 * todo: Добавить регрессионные проверки harness ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_05797da1abbb * todo: Прогнать локальные проверки и полные e2e ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_c293b80879f5 * todo: Провести финальный review diff и подготовить коммит ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_25b97956ddef * test(e2e): stabilize live runs after rebase | 2 months ago | |
test(e2e): let the chat's placement motion settle before clicking visualize card controls (#1124) A file visualization's fixed-height preview is taller than the transcript beside the default bottom terminal, so its toolbar sits above the fold at the latest edge. Playwright's scroll-into-view is not reader input, so while the chat's mount hold (or the alignment that follows content growth) is still running it restores the latest edge between mousedown and mouseup, or moves the preview iframe under the pointer, and the click is lost while the action reports success: the view toggle, design width, full screen, Open in tab and Show diff anyway never apply, and tool-step expansions in tool-file-links miss the same way. Move waitForScrollStability from chat-scroll.spec.ts into fixtures/chatScroll.ts and add clickTranscriptControl, which waits for data-scroll-moving="false" and stable scroll geometry before clicking. Both specs click every transcript control through it; the comparison tests first wait for the card's two preview frames, and the full-screen diff waits for the dialog to hide before Open in tab. | 6 days ago | |
feat(web): first-run onboarding — intro, privacy and connect-and-verify wizard, recommendations from detected accounts, and Start a new project (#808) * squash: onboarding-first-run * feat(onboarding): harden account detection, share the consent draft between the dialog and the wizard's privacy step, let custom presets place the TODO List, and align the layout e2e specs with Balanced opening on it * fix(onboarding): advance the privacy step only after its consent save lands, verify a provider connected before the wizard reaches Connect, and record the new-project Default exception in the top-level docs * fix(onboarding): keep provider status in one store snapshot invalidated by every change, adopt a consent choice confirmed elsewhere, fence the new-project setup continuation, offer API keys on every provider, and make the onboarding e2e deterministic * test(projects): write the rollback test's hooksPath with forward slashes so git can parse it on Windows * fix(locales): drop the Spanish strings for the removed login Submit key and the reworded open-project subtitle * fix(locales): drop the Japanese strings for the removed login Submit key and the reworded open-project subtitle * Harden onboarding connect: auto-recheck empty model lists, cancel a pending login on skip, split the wizard into step components, drop the viewport-cap table, and emit a created project once * Verify a connection against an awaited model.refresh when the cached model.list is empty, instead of a fixed-delay re-read * fix(onboarding): re-read provider status on Welcome entry and on another login's success, and hand off only from a current snapshot * test(onboarding): settle the host before each cold start, run every teardown step, and drop wall-clock and inherited git-config dependence * fix(onboarding): take the connect dialog's baseline from its first report, so a sign-in that lands before the opening re-read still hands off * test(onboarding): read config.json tolerantly inside polls, remove the projects folder with retries, and count the toast before it auto-dismisses * test(shared): wait for the Central watcher's second re-arm instead of a fixed 5 ms sleep --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 10 days ago | |
feat(web): restore bounded passive auto-open for live/unfinished chats (#361) Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 2 months ago | |
fix(desktop): make Windows updates install and show the install journey across the restart (#803) * fix(desktop): start the host outside the app bundle so Windows updates can swap it * docs(desktop): record why startup leaves the app bundle before the host boots * feat(desktop): report install steps, a saved log, and the result after an update relaunch * feat(web): show the installer dialog, the update result, and failure recovery across the restart * feat(desktop): show a startup splash that names a pending update until the app window is ready * fix(desktop): close the startup splash when the main window closes early, and track one install attempt * docs: describe the native update install journey in the desktop, web and contracts specs and README * refactor: render the update install journey and startup splash text from locale keys * fix: carry update install-log entries as structured events the web translates | 16 days ago | |
feat: always show Remote and Pull request in the topbar with the branch's next step (#771) * docs(specs): specify always-on Remote and Pull request topbar segments * feat(server): report branch status without a review and push without a PR * feat(web): always show Remote and Pull request in the topbar with their next action * test(e2e): cover publishing, base pushes, and Open PR from the topbar * fix(server): pick the forge from origin's push URL and bind workspace.push to the shown branch * fix(server): measure a split origin's divergence against its push repository * fix(server): read a split origin's push tip on cached branch-status reads too * fix(web): bind the topbar ship runner to its workspace * fix: keep Push updates on the plan body and drop the pull command for split origins * chore: move branch status to protocol v87 after workspace read state took v86 * fix: bind Open PR and retries to the shown branch, target gh at the push repository, and report an unreadable push repository as unknown * fix(server): keep the plan body for pre-v87 clients' source-less PR previews | 17 days ago | |
feat: GitHub review details and actions in the topbar (#776) * feat(contracts): describe GitHub review details and review actions at v88 * feat(server): read draft, checks, review decision and merge settings for an open PR * feat(server): mark ready, merge, reopen, and delete a PR's remote branch * feat(web): show checks and review decision in the topbar, offer review actions, and poll running checks * test(e2e): fake gh and a GitHub-looking origin to cover review actions in the topbar * docs: describe the topbar's GitHub review details and actions * fix: keep review actions off forks' same-named branches and never delete a queued PR's branch * fix(web): keep polling briefly after a ship action until its checks appear * chore: name review actions v88 in specs and tests after branch status took v87 * fix(server): lease remote branch deletion to the tip it was checked against and skip forks' settled PRs * fix(server): ask the push repository whether a PR still uses the branch before deleting it * fix(web): name the PR's current base in the merge dialog, not the workspace's | 17 days ago | |
feat: always show Remote and Pull request in the topbar with the branch's next step (#771) * docs(specs): specify always-on Remote and Pull request topbar segments * feat(server): report branch status without a review and push without a PR * feat(web): always show Remote and Pull request in the topbar with their next action * test(e2e): cover publishing, base pushes, and Open PR from the topbar * fix(server): pick the forge from origin's push URL and bind workspace.push to the shown branch * fix(server): measure a split origin's divergence against its push repository * fix(server): read a split origin's push tip on cached branch-status reads too * fix(web): bind the topbar ship runner to its workspace * fix: keep Push updates on the plan body and drop the pull command for split origins * chore: move branch status to protocol v87 after workspace read state took v86 * fix: bind Open PR and retries to the shown branch, target gh at the push repository, and report an unreadable push repository as unknown * fix(server): keep the plan body for pre-v87 clients' source-less PR previews | 17 days ago | |
fix(web): link usage page and provider settings on subscription-limit errors (#1175) * fix(web): recognize Claude extra-usage and ChatGPT subscription-limit errors * fix(web): offer usage page and provider settings instead of Try again on subscription-limit errors * docs(web): record subscription-limit error handling in the chat spec | 4 days ago | |
chore(e2e): type-check the e2e tree in typecheck:root (#1064) Add e2e/tsconfig.json (root config with an e2e include) and run it as an extra tsc step of typecheck:root. Fix the stale and trivial errors it surfaced: the removed "endTurn" stop reason, untyped evaluate element parameters, unchecked index reads, ask/visualize tool arguments that must be JSON objects, and exact-optional shard options. reload-navigation.spec.ts stays excluded as the remaining to-do. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 7 days ago | |
feat(web): merge Changes into a VS Code-like Files panel (#779) * fix(server): report readDir paths with forward slashes on every OS On Windows path.relative() produced src\auth while git status and watch events use src/auth, so the Files tree could only match separator-free top-level names to changed paths. * feat(server): mark git-ignored entries in fs.readDir The host fills FileNode.gitignored from git check-ignore so the Files tree can dim ignored files and folders; a non-repository or a failed check marks nothing. * feat(web): merge Changes into a VS Code-like Files panel Files gains an All | Changed switch: Changed is the former Changes tool; All marks changed files and every ancestor folder, dims git-ignored entries, highlights and auto-reveals the active file, offers collapse-all, and remembers expanded folders and scroll per workspace across reloads. The Changes side tool is removed from the layout model and presets. * test(e2e): reveal Files in All mode and adapt specs to the merged panel revealWorkbenchTool("files") now also selects All, as "changes" selects Changed; anchored file-row regexes tolerate the new status letter; the pristine Balanced layout has Review instead of Changes; the Review panel now stays mounted beside Files, so its sections keep their expansion. * test(e2e): expect the template file to be auto-revealed in the Files tree Opening the template makes it the active file, so Files' All mode already expands .pi/prompts and highlights it; clicking the folder would fold it again. * fix(web): start the Changed list at the top when the compare target changes The Changed views' remembered scroll was tagged only with the diff scope, so after picking another target branch elsewhere the list restored an offset from the previous target's list. The scroll identity now reuses the scope + target key the highlight already uses. * fix(web): apply review findings on the Files tree - A compact folder row takes the git-ignored state of the deepest folder it represents, so outer/cache is dimmed when only cache/ is ignored. - One statusLetter (chat/planView) serves the Files tree, the Changes review tab and plan rows; untracked files read U everywhere. - Shared helpers instead of copies: lib ancestorPaths and isRecord, one reveal-Files-in-Changed-mode store patch, and the renderers' validating viewStateScrollTop. | 17 days ago | |
feat: let users choose the interface and code fonts (#1092) * feat: let users choose the interface and code fonts Appearance gains Fonts and Custom fonts groups: interface and code fonts from the bundled faces, the host's installed fonts or uploaded files, fallback fonts, code size, ligatures and a text-weight slider. typography.json stays the only source of defaults; a new web fonts module overrides only the user-adjustable tokens on :root. * fix(fonts): harden font settings after review - Render the font pickers into the Settings dialog so they scroll and stay inside it. - Keep an open Pierre comment when the code size changes: hand the new row height to the connected Pierre instances instead of remounting the surface. - Settings drafts no longer let a host echo undo an unsent edit or a newer save; xterm and Mermaid follow the code font through a latest-wins watcher; the custom font cap is checked again right before the rename. - Share the Settings folder row, draft input, save, sub-heading, tag and range slider, and one host data-folder poller for locales, themes and fonts. - Name every picker and reset for assistive tech; the preview is inert on the chat canvas. - Translate the font strings into Russian. - Make the font e2e independent of installed fonts and reset settings over the wire. * fix(fonts): wait for the resolved weight and reach the launched host in e2e - fontsLoaded loads the resolved weight too, so Monaco, xterm and Mermaid measure the face they will draw with, not the regular one. - The font e2e talks to the host the page was launched against, so it runs in the binary and desktop artifact modes. * fix(visualize): wait for the code face before the first Mermaid render | 7 days ago | |
fix(desktop): make Windows updates install and show the install journey across the restart (#803) * fix(desktop): start the host outside the app bundle so Windows updates can swap it * docs(desktop): record why startup leaves the app bundle before the host boots * feat(desktop): report install steps, a saved log, and the result after an update relaunch * feat(web): show the installer dialog, the update result, and failure recovery across the restart * feat(desktop): show a startup splash that names a pending update until the app window is ready * fix(desktop): close the startup splash when the main window closes early, and track one install attempt * docs: describe the native update install journey in the desktop, web and contracts specs and README * refactor: render the update install journey and startup splash text from locale keys * fix: carry update install-log entries as structured events the web translates | 16 days ago | |
feat(web): let users add their own themes from ~/.thinkrail/themes (#937) * feat(contracts): add the user theme files wire surface * feat(server): serve and watch user theme files * feat(web): install user theme files into the theme catalog * feat(web): show the custom themes folder in Appearance settings * test(e2e): cover user theme files from folder to picker * fix(themes): bound the first-paint cache and harden user theme reading * docs(specs): record user theme files as runtime data * docs(specs): move user theme files to protocol v92 * feat(web): namespace user theme ids under user. * fix(web): pick fallback and derived pair themes from bundled themes only * fix(locales): word the theme fallback notice for deleted user themes * docs(themes): record the manifest compatibility rule and the deferred theme work * fix(web): report an over-long user theme id through a locale key | 11 days ago | |
feat(visualize): show workspace files and before/after mockups in chat through the shared resource renderers (#784) * feat(pi-visualize): show workspace files and before/after diffs with type "file" * feat(visualize): show workspace files and before/after mockups in chat through the shared resource renderers * fix(resource-renderers): guard large comparisons, canonicalize preview paths, resolve original-side assets * chore(web): drop the unused diff dependency and stale path-helper notes after the largeDiff move * fix(resource-renderers): give each Monaco editor mount its own model * fix(resource-renderers): give the chat scope its own context and keep preview reads stable * fix(web): re-read file previews after a reconnect and share the fs-change rule with file tabs * fix(server): send a review that names its chat as one package * fix(resource-renderers): keep preview view state through cards, full screen and comparison tabs * fix(visualize): match the host read guard in file checks and reject a before that names the same file * refactor(resource-renderers): share renderer toggles and the guarded diff mount across panes * refactor(resource-renderers): share renderer selection and content derivation across surfaces * refactor(web): make file comparisons their own diff-tab variant * refactor(resource-renderers): share element-size measuring and fit HTML previews before paint * test(web): pin the preview's path identity to the app's and drop the card's second canonicalization * perf(web): keep review thread arrays stable per file so an unrelated comment re-renders no renderer * refactor(resource-renderers): trim the barrel to what consumers use and document live card history * fix(resource-renderers): read a host answer without file metadata as provisional text * fix(resource-renderers): let an HTML preview width follow a shared view state * fix(web): name each file in a multi-file review package in the chat * fix(resource-renderers): mark a preview's retained content stale when a refresh fails * docs(resource-renderers): set the stale notice text in a code span --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 17 days ago | |
test(e2e): let the chat's placement motion settle before clicking visualize card controls (#1124) A file visualization's fixed-height preview is taller than the transcript beside the default bottom terminal, so its toolbar sits above the fold at the latest edge. Playwright's scroll-into-view is not reader input, so while the chat's mount hold (or the alignment that follows content growth) is still running it restores the latest edge between mousedown and mouseup, or moves the preview iframe under the pointer, and the click is lost while the action reports success: the view toggle, design width, full screen, Open in tab and Show diff anyway never apply, and tool-step expansions in tool-file-links miss the same way. Move waitForScrollStability from chat-scroll.spec.ts into fixtures/chatScroll.ts and add clickTranscriptControl, which waits for data-scroll-moving="false" and stable scroll geometry before clicking. Both specs click every transcript control through it; the comparison tests first wait for the card's two preview frames, and the full-screen diff waits for the dialog to hide before Open in tab. | 6 days ago | |
test(e2e): make real-Central agent runs deterministic (#352) * todo: Зафиксировать одобренный дизайн в task-spec и e2e SPEC ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_ea68a25a38b5 * todo: Реализовать изолированный real-Central agent harness ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_6c99016803f9 * todo: Добавить регрессионные проверки harness ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_05797da1abbb * todo: Прогнать локальные проверки и полные e2e ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_c293b80879f5 * todo: Провести финальный review diff и подготовить коммит ThinkRail-Todo: 01a052f0-1e41-7fb5-b1c8-1749c932a4f1/t_25b97956ddef * test(e2e): stabilize live runs after rebase | 2 months ago | |
feat(web): first-run onboarding — intro, privacy and connect-and-verify wizard, recommendations from detected accounts, and Start a new project (#808) * squash: onboarding-first-run * feat(onboarding): harden account detection, share the consent draft between the dialog and the wizard's privacy step, let custom presets place the TODO List, and align the layout e2e specs with Balanced opening on it * fix(onboarding): advance the privacy step only after its consent save lands, verify a provider connected before the wizard reaches Connect, and record the new-project Default exception in the top-level docs * fix(onboarding): keep provider status in one store snapshot invalidated by every change, adopt a consent choice confirmed elsewhere, fence the new-project setup continuation, offer API keys on every provider, and make the onboarding e2e deterministic * test(projects): write the rollback test's hooksPath with forward slashes so git can parse it on Windows * fix(locales): drop the Spanish strings for the removed login Submit key and the reworded open-project subtitle * fix(locales): drop the Japanese strings for the removed login Submit key and the reworded open-project subtitle * Harden onboarding connect: auto-recheck empty model lists, cancel a pending login on skip, split the wizard into step components, drop the viewport-cap table, and emit a created project once * Verify a connection against an awaited model.refresh when the cached model.list is empty, instead of a fixed-delay re-read * fix(onboarding): re-read provider status on Welcome entry and on another login's success, and hand off only from a current snapshot * test(onboarding): settle the host before each cold start, run every teardown step, and drop wall-clock and inherited git-config dependence * fix(onboarding): take the connect dialog's baseline from its first report, so a sign-in that lands before the opening re-read still hands off * test(onboarding): read config.json tolerantly inside polls, remove the projects folder with retries, and count the toast before it auto-dismisses * test(shared): wait for the Central watcher's second re-arm instead of a fixed 5 ms sleep --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 10 days ago | |
fix(web): drop workspaces removed while a window was disconnected on reconnect (#1195) * fix(web): drop workspaces removed while a window was disconnected on reconnect Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(web): cancel the MCP login of a workspace retired on reconnect Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: yo mooo <yomooo2004@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> | 2 days ago | |
Add inline workspace renaming (#368) * todo: Draft and validate the workspace rename design ThinkRail-Todo: 01a05780-07db-7fab-84e5-8c15dd9f26ac/t_ce80b5fbbd0f * todo: Implement rename across host, wire, and web UI ThinkRail-Todo: 01a05780-07db-7fab-84e5-8c15dd9f26ac/t_5ae68fb12d85 * todo: Add automated coverage for rename behavior ThinkRail-Todo: 01a05780-07db-7fab-84e5-8c15dd9f26ac/t_f2851059300f * todo: Address review findings, rerun gates, and review the final diff ThinkRail-Todo: 01a05780-07db-7fab-84e5-8c15dd9f26ac/t_5f9f3eda1a79 * todo: Revise the rename design for inline display-only editing ThinkRail-Todo: 01a05780-07db-7fab-84e5-8c15dd9f26ac/t_e853ac543a48 * todo: Implement inline display-only workspace renaming ThinkRail-Todo: 01a05780-07db-7fab-84e5-8c15dd9f26ac/t_6f438474fe9e * todo: Reverify and self-review the revised branch ThinkRail-Todo: 01a05780-07db-7fab-84e5-8c15dd9f26ac/t_79d55f211471 --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 2 months ago | |
feat(web): edge tool rails that mirror the pane model, Minimal themes, and remount-free panel toggles (#680) Projects, Specs, Files, Changes and Review move from per-pane tab strips to persistent left/right/bottom edge rails. The rail renders the layout's group model instead of hiding it: entries come in clusters, one per pane in document order, separated by a hairline; one pressed icon per open pane; removed tools sit dimmed in a trailing cluster. Drops follow the clusters (entry halves insert into that pane, a "New pane here" zone at every boundary creates one) and the pane header title drags its tool. Terminal groups keep named session tabs, and Project Home composes the same rail and pane header. Tools and terminals never share a pane (canJoinAuxiliaryGroup): enforced in every mutation, in normalization on load and across workspaces, in passive terminal placement and preset remaps. The mixed-order anchors (beforeToolByTabId) and terminal recall (lastTerminalByGroup) go with it. Menus name a pane by its contents, hide commands that can never apply to a tab, and the layout owns New terminal / New terminal pane below. Panels no longer remount on shape changes: structural-only panel-group keys, committed sizes re-applied imperatively, and topology-dependent minimums relaxed for the one membership-change commit (useTopologySettled). Themes: the standard Light/Dark and High Contrast palettes are unchanged; the quieter Minimal palettes ship as Minimal Light / Minimal Dark. A theme is a palette and role usage never varies by theme. The rails live in the split workbench renderer (workbenchRails.tsx and friends); the interface design lab that produced the direction stays out of the repository (design/ is gitignored). The spec-graph index also skips the scratch directories other assistants keep in a project (.claude, .superpowers, .gsd, .mockups, .playwright-mcp), whose plans and generated skills carry spec-shaped frontmatter. README screenshots are re-captured from this head (the real project in an isolated host: workbench light/dark and the Specs tool), with alt text that names the edge rails. | 28 days ago | |
Add inline workspace renaming (#368) * todo: Draft and validate the workspace rename design ThinkRail-Todo: 01a05780-07db-7fab-84e5-8c15dd9f26ac/t_ce80b5fbbd0f * todo: Implement rename across host, wire, and web UI ThinkRail-Todo: 01a05780-07db-7fab-84e5-8c15dd9f26ac/t_5ae68fb12d85 * todo: Add automated coverage for rename behavior ThinkRail-Todo: 01a05780-07db-7fab-84e5-8c15dd9f26ac/t_f2851059300f * todo: Address review findings, rerun gates, and review the final diff ThinkRail-Todo: 01a05780-07db-7fab-84e5-8c15dd9f26ac/t_5f9f3eda1a79 * todo: Revise the rename design for inline display-only editing ThinkRail-Todo: 01a05780-07db-7fab-84e5-8c15dd9f26ac/t_e853ac543a48 * todo: Implement inline display-only workspace renaming ThinkRail-Todo: 01a05780-07db-7fab-84e5-8c15dd9f26ac/t_6f438474fe9e * todo: Reverify and self-review the revised branch ThinkRail-Todo: 01a05780-07db-7fab-84e5-8c15dd9f26ac/t_79d55f211471 --------- Co-authored-by: danyaberezun <danya.berezun@gmail.com> | 2 months ago |
| Files | Last commit | Last update |
|---|---|---|
| 6 days ago | ||
| 5 days ago | ||
| 5 days ago | ||
| 23 days ago | ||
| 1 month ago | ||
| 23 days ago | ||
| 1 day ago | ||
| 7 days ago | ||
| 5 days ago | ||
| 1 month ago | ||
| 2 months ago | ||
| 5 days ago | ||
| 1 month ago | ||
| 8 days ago | ||
| 6 days ago | ||
| 5 days ago | ||
| 1 day ago | ||
| 10 days ago | ||
| 1 day ago | ||
| 1 day ago | ||
| 24 days ago | ||
| 10 days ago | ||
| 12 days ago | ||
| 27 days ago | ||
| 6 days ago | ||
| 10 days ago | ||
| 30 days ago | ||
| 2 months ago | ||
| 1 month ago | ||
| 23 days ago | ||
| 9 days ago | ||
| 9 days ago | ||
| 9 days ago | ||
| 30 days ago | ||
| 1 month ago | ||
| 6 days ago | ||
| 5 days ago | ||
| 10 days ago | ||
| 22 days ago | ||
| 2 months ago | ||
| 11 days ago | ||
| 2 months ago | ||
| 7 days ago | ||
| 2 months ago | ||
| 23 days ago | ||
| 17 days ago | ||
| 28 days ago | ||
| 13 days ago | ||
| 13 days ago | ||
| 7 days ago | ||
| 11 days ago | ||
| 13 days ago | ||
| 1 month ago | ||
| 19 days ago | ||
| 1 day ago | ||
| 8 days ago | ||
| 17 days ago | ||
| 7 days ago | ||
| 2 months ago | ||
| 2 months ago | ||
| 3 days ago | ||
| 3 days ago | ||
| 2 months ago | ||
| 29 days ago | ||
| 2 months ago | ||
| 13 days ago | ||
| 2 months ago | ||
| 1 month ago | ||
| 23 days ago | ||
| 2 months ago | ||
| 2 months ago | ||
| 5 days ago | ||
| 16 days ago | ||
| 6 days ago | ||
| 23 days ago | ||
| 17 days ago | ||
| 2 months ago | ||
| 28 days ago | ||
| 28 days ago | ||
| 28 days ago | ||
| 19 days ago | ||
| 20 days ago | ||
| 7 days ago | ||
| 23 days ago | ||
| 23 days ago | ||
| 2 months ago | ||
| 2 months ago | ||
| 1 month ago | ||
| 23 days ago | ||
| 1 day ago | ||
| 10 days ago | ||
| 27 days ago | ||
| 7 days ago | ||
| 6 days ago | ||
| 1 day ago | ||
| 10 days ago | ||
| 23 days ago | ||
| 5 days ago | ||
| 1 day ago | ||
| 5 days ago | ||
| 5 days ago | ||
| 5 days ago | ||
| 2 months ago | ||
| 13 days ago | ||
| 1 month ago | ||
| 5 days ago | ||
| 10 days ago | ||
| 10 days ago | ||
| 13 days ago | ||
| 20 days ago | ||
| 1 month ago | ||
| 10 days ago | ||
| 14 days ago | ||
| 2 months ago | ||
| 27 days ago | ||
| 22 days ago | ||
| 4 days ago | ||
| 2 months ago | ||
| 5 days ago | ||
| 5 days ago | ||
| 5 days ago | ||
| 5 days ago | ||
| 5 days ago | ||
| 7 days ago | ||
| 28 days ago | ||
| 7 days ago | ||
| 7 days ago | ||
| 22 days ago | ||
| 2 months ago | ||
| 24 days ago | ||
| 1 month ago | ||
| 7 days ago | ||
| 28 days ago | ||
| 10 days ago | ||
| 2 months ago | ||
| 28 days ago | ||
| 26 days ago | ||
| 2 months ago | ||
| 27 days ago | ||
| 27 days ago | ||
| 18 days ago | ||
| 2 months ago | ||
| 13 days ago | ||
| 2 months ago | ||
| 14 days ago | ||
| 7 days ago | ||
| 10 days ago | ||
| 23 days ago | ||
| 2 months ago | ||
| 6 days ago | ||
| 10 days ago | ||
| 2 months ago | ||
| 16 days ago | ||
| 17 days ago | ||
| 17 days ago | ||
| 17 days ago | ||
| 4 days ago | ||
| 7 days ago | ||
| 17 days ago | ||
| 7 days ago | ||
| 16 days ago | ||
| 11 days ago | ||
| 17 days ago | ||
| 6 days ago | ||
| 2 months ago | ||
| 10 days ago | ||
| 2 days ago | ||
| 2 months ago | ||
| 28 days ago | ||
| 2 months ago |