Skip to content

feat: repaint the desktop chrome, adopt the DeepSeek tokens, and add thinking effort levels - #97

Merged
elkaix merged 27 commits into
mainfrom
fix/desktop-release-changesets
Aug 17, 2026
Merged

feat: repaint the desktop chrome, adopt the DeepSeek tokens, and add thinking effort levels#97
elkaix merged 27 commits into
mainfrom
fix/desktop-release-changesets

Conversation

@elkaix

@elkaix elkaix commented Aug 17, 2026

Copy link
Copy Markdown
Member

Related Issue

No issue. The problem is described below.

Problem

Several things had drifted at once across the desktop app and the web UI:

  • The desktop window painted its own chrome, lost the native macOS corners and
    the vibrancy material, and shipped without the release changesets it needed.
  • The web UI's colours came from ad-hoc values. In the light theme the secondary
    and meta label ramp measured 3.71:1, 2.13:1 and 1.26:1 on white — all below
    WCAG AA.
  • The composer offered a single thinking on/off row that always sent high,
    even though the model catalog knows which effort levels each model supports
    and every other renderer already offers the full set.
  • The model quick-switch menu had a height floor but no ceiling, so a provider
    with roughly twenty models filled the whole window.
  • The marketing site was removed, but a desktop test still read
    apps/site/src/App.vue and an oauth comment still pointed at it.
  • The legacy migration package was removed, leaving stale references behind.

What changed

Desktop. The macOS window keeps its native frame and vibrancy material, both
palettes were repainted, the window is opaque on every platform, and the release
workflow now pins its actions to commit SHAs and publishes to the dedicated
desktop release repository.

Design tokens. The web UI adopts the vendored DeepSeek token sheets. The
light palette overrides three label tokens, because the vendored ramp fails AA
on a white page and the neutral scale has no step between -600 and -700; the
replacements measure 5.00:1, 4.57:1 and 3.25:1 and keep the ramp ordering. Dark
mode keeps the vendored aliases untouched.

Thinking effort. The model catalog now carries support_efforts and
adaptive_thinking through all five layers (zod schema → toProtocolModel
WireModeltoAppModelAppModel), so the web client finally knows what a
model offers. The composer lists those levels, clamps a requested level to the
nearest lower supported one, and shows the effective level in the footer pill.
The rules mirror packages/node-sdk/src/thinking-levels.ts; the web app has no
dependency on the SDK, so the mirror is deliberate.

Menu height. The quick-switch dropdown is capped at 360px and scrolls.

Cleanup. The marketing site references, the legacy migration fallout, and
two stale attachment-chip theme overrides are gone. The prompt anchor rail's
setting hint and code comment now describe what the rail actually draws.

Verification

  • Full suite: 10285 passed, 71 skipped, 2 todo — green via the pre-push hook.
  • pnpm --filter @pymodel/pythinker-web run typecheck — exit 0.
  • pnpm run lint — exit 0, no errors.
  • Light-theme contrast measured live in Chromium, not calculated only.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

elkaix added 27 commits August 16, 2026 11:34
The Host port fix and the dedicated desktop update channel both merged without a changeset, so neither is versioned or recorded in the desktop changelog.
Lead with a dark hero that presents the desktop app and an app-window preview, make the nav react to scroll, expand the cursor-aware particle field, and drop the CSS bubble decorations and the separate desktop showcase section.
…ent tag

The site hero moved the desktop anchor from a section to the hero header, so the packaging-config test extracted a null block. Bind the Windows assertion to the desktop download entry instead, so it cannot pass on the CLI install rows.
The macOS sidebar renders on a translucent light surface, but the dark palette picked secondary text colours for an opaque dark background, so the workspace header, session timestamps, and the settings row disappeared. Raise the three faded tokens in both dark blocks, keeping their hierarchy.
The dialog shell had a fixed height with no overflow clipping, and its body only scrolled below 640px. On desktop the body overflowed, pushing the actions row and footer outside the shell, so the bottom border drew across the Cancel button.
…s pill

The dropdown opened upward with no height limit, so a long provider model list ran past the top of the window and the models above the fold could not be reached. Measure the pill on open and cap the menu, letting it scroll.
The macOS window used vibrancy over a transparent background, so the dark palette rendered on a translucent light surface its secondary colours were never designed for. Drop vibrancy and transparency and paint the palette's own background colour, keeping the per-platform window chrome.
Remap the terminal and modern token blocks to the DeepSeek design system
palette, and repaint the native window background to match. The pythinker
brand theme keeps its own palette.

Wire the new APNG mascots into the thinking indicator, alternating between
the laptop and review poses after each full play, and make the logo play
its waving animation twice before holding its first frame. Re-time all
three so they no longer read as jerky.

Correct the logo image paths, which pointed outside the served public
directory and had been resolving to 404.
Encode the failed and idle animations as APNGs from their source frames
and drop the two sprite sheets they were the last readers of. The mascot
now has one rendering path with no CSS step animation, and the shipped
assets shrink by roughly 780 KB.
…indow

Replace the light and dark palettes with values measured from the target
design, keeping the pythinker brand theme on its own colours. Light mode
mirrors the dark structure rather than copying it, since the reference is
dark only.

Restore vibrancy on macOS so the sidebar reads as translucent again, and
reserve the top strip of the expanded sidebar for the traffic lights,
which the collapsed rail already did. Only that strip drags the window.

Keep Windows framed with its caption buttons at the top right, and keep
Linux frameless so its custom title bar still applies.
The pill was positioned against the whole conversation column, so its
bottom edge landed on the composer toolbar and covered the mode and
model controls. Anchor it to a wrapper that ends where the composer
begins, so it clears the composer at any composer height.
Run the macOS window frameless and transparent, and paint the traffic lights, rounded card, and hairline border in the app itself instead of asking AppKit for them. Windows keeps its native frame and caption buttons.
…rial theme

Keep the native frame with a hidden-inset title bar so macOS draws the traffic lights, rounded corners, and shadow, matching the reference shell. The renderer-drawn buttons and their IPC are removed. Push the resolved color scheme to nativeTheme so the sidebar vibrancy follows the app theme instead of the system appearance, which left dark-mode meta text on a light material. The idle mascot now plays twice and re-arms every 20 seconds.
Vendor design-platform.css, scrollbar.css, and shiki.css from @deepseek-ai/dsh-client-ui-theme (MIT) and redefine the app's flat tokens as references into them, so both themes come from one upstream source instead of hand-copied literals. The UI font stack stops being monospace. An inline pre-paint script resolves the dark attribute so dark users do not flash light.
Add a Desktop App guide covering download, first launch, tray behavior,
updates, and the loopback Host port, and link it from getting started.

Remove the pythinker-cli migration feature completely: the `pythinker
migrate` subcommand, the first-launch migration prompt, the migration TUI
screen, the `[imported]` session badge, the dashboard filter for imported
sessions, and the `@pymodel/migration-legacy` package. The migration guide
and its reference entry are deleted with it.
Carry the in-progress desktop window appearance work, the web chat and
sidebar refinements, the general session mode profile, and their tests.
Regenerate pnpm-lock.yaml so a frozen-lockfile install no longer fails on the
deleted @pymodel/migration-legacy dependency, drop both deleted packages from
the changeset ignore list and its documentation, correct the desktop guide
(arm64 DMG name, tray animation runs during Host startup, signing wording),
and restore the two non-migration assertions the cleanup removed: terminal
tracking disposal on startup failure and a loud invalid-config error.
Every third-party and GitHub-owned action in the release workflow was pinned to
a mutable tag, so a retagged release could change what runs on a job that holds
contents:write, id-token:write and the npm publishing identity. Pin all sixteen
references to full commit SHAs with the version in an adjacent comment, matching
the convention already used in desktop-release.yml.
The composer offered a single thinking on/off row and always sent 'high'.
It now lists the effort levels the current model declares (off, minimal,
low, medium, high, xhigh, max), filtered and clamped by the same rules the
node SDK applies to the terminal and VS Code renderers, and the footer pill
shows the effective level instead of the fixed word 'thinking'.

The dropdown also had a height floor but no ceiling, so a provider with
many models filled the whole window. It is now capped at 360px and scrolls.
The Modern and Pythinker themes still forced the old radii on .att-chip and
.att-thumb. The composer now draws round 18px chips with square-cropped
thumbnails, so the overrides only fought it.
The setting hint and the composable comment still promised a proportional
TOC with a viewport indicator. The rail draws one tick per prompt and marks
the tick nearest the middle of the view.
The desktop packaging spec read apps/site/src/App.vue to check the download
links, which fails now that the site is gone, and an oauth comment pointed
at the same tree.
general.md is loaded through systemPromptPath and rendered by renderPrompt,
exactly like system.md, so its {{ ROLE_ADDITIONAL }} and {{ PYTHINKER_SKILLS }}
placeholders are resolved before the model sees them.
@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@elkaix, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 13 minutes

Limit details: You’ve used all 3 included reviews currently available under your plan.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 637b8a5b-d4a7-40c7-8d54-c6877b9bf7bb

📥 Commits

Reviewing files that changed from the base of the PR and between ac29bc4 and d441c63.

⛔ Files ignored due to path filters (44)
  • apps/pythinker-web/public/brand/mascot-failed.png is excluded by !**/*.png, !**/*.png
  • apps/pythinker-web/public/brand/mascot-idle-strip.png is excluded by !**/*.png, !**/*.png
  • apps/pythinker-web/public/brand/mascot-idle.png is excluded by !**/*.png, !**/*.png
  • apps/pythinker-web/public/brand/mascot-jumping.png is excluded by !**/*.png, !**/*.png
  • apps/pythinker-web/public/brand/mascot-laptop.png is excluded by !**/*.png, !**/*.png
  • apps/pythinker-web/public/brand/mascot-review.png is excluded by !**/*.png, !**/*.png
  • apps/pythinker-web/public/brand/mascot-running-left.png is excluded by !**/*.png, !**/*.png
  • apps/pythinker-web/public/brand/mascot-running-right.png is excluded by !**/*.png, !**/*.png
  • apps/pythinker-web/public/brand/mascot-states.png is excluded by !**/*.png, !**/*.png
  • apps/pythinker-web/public/brand/mascot-waving.png is excluded by !**/*.png, !**/*.png
  • apps/site/public/apple-touch-icon.png is excluded by !**/*.png, !**/*.png
  • apps/site/public/bimi-logo.svg is excluded by !**/*.svg
  • apps/site/public/brand/anthropic.svg is excluded by !**/*.svg
  • apps/site/public/brand/apple.svg is excluded by !**/*.svg
  • apps/site/public/brand/aws.svg is excluded by !**/*.svg
  • apps/site/public/brand/deepseek.svg is excluded by !**/*.svg
  • apps/site/public/brand/gemini.svg is excluded by !**/*.svg
  • apps/site/public/brand/github.svg is excluded by !**/*.svg
  • apps/site/public/brand/groq.svg is excluded by !**/*.svg
  • apps/site/public/brand/icon.svg is excluded by !**/*.svg
  • apps/site/public/brand/jetbrains.svg is excluded by !**/*.svg
  • apps/site/public/brand/lmstudio.svg is excluded by !**/*.svg
  • apps/site/public/brand/mcp.svg is excluded by !**/*.svg
  • apps/site/public/brand/meta.svg is excluded by !**/*.svg
  • apps/site/public/brand/mistral.svg is excluded by !**/*.svg
  • apps/site/public/brand/npm.svg is excluded by !**/*.svg
  • apps/site/public/brand/ollama.svg is excluded by !**/*.svg
  • apps/site/public/brand/openai.svg is excluded by !**/*.svg
  • apps/site/public/brand/pythinker_animated.svg is excluded by !**/*.svg
  • apps/site/public/brand/visualstudiocode.svg is excluded by !**/*.svg
  • apps/site/public/brand/windows11.svg is excluded by !**/*.svg
  • apps/site/public/favicon.ico is excluded by !**/*.ico
  • apps/site/public/favicon.svg is excluded by !**/*.svg
  • apps/site/public/icon-192.png is excluded by !**/*.png, !**/*.png
  • apps/site/public/icon-512.png is excluded by !**/*.png, !**/*.png
  • apps/site/public/icon.svg is excluded by !**/*.svg
  • apps/site/public/og.jpg is excluded by !**/*.jpg
  • apps/site/public/pythinker_animated.svg is excluded by !**/*.svg
  • apps/site/public/pythinker_desktop.png is excluded by !**/*.png, !**/*.png
  • apps/site/public/pythinker_desktop.webm is excluded by !**/*.webm
  • apps/site/public/pythinker_desktop.webp is excluded by !**/*.webp
  • apps/site/public/vscode_img.jpeg is excluded by !**/*.jpeg
  • packages/migration-legacy/test/sessions/__snapshots__/fixtures.snapshot.test.ts.snap is excluded by !**/*.snap, !**/*.snap
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !**/pnpm-lock.yaml
📒 Files selected for processing (226)
  • .agents/skills/gen-changesets/SKILL.md
  • .changeset/README.md
  • .changeset/config.json
  • .changeset/dark-sidebar-contrast.md
  • .changeset/desktop-dedicated-update-channel.md
  • .changeset/desktop-pin-host-port.md
  • .changeset/general-session-mode.md
  • .changeset/model-catalog-thinking-metadata.md
  • .changeset/remove-legacy-migration.md
  • .github/workflows/release.yml
  • apps/dashboard/server/src/lib/session-store.ts
  • apps/dashboard/server/test/lib/session-store.test.ts
  • apps/dashboard/web/src/components/state/StateTab.tsx
  • apps/desktop/src/main.ts
  • apps/desktop/src/preload.ts
  • apps/desktop/src/window-options.ts
  • apps/desktop/tests/packaging-config.spec.ts
  • apps/desktop/tests/window-appearance.spec.ts
  • apps/pythinker-code/package.json
  • apps/pythinker-code/src/cli/commands.ts
  • apps/pythinker-code/src/cli/run-shell.ts
  • apps/pythinker-code/src/main.ts
  • apps/pythinker-code/src/migration/badge.ts
  • apps/pythinker-code/src/migration/command.ts
  • apps/pythinker-code/src/migration/detect-pending.ts
  • apps/pythinker-code/src/migration/index.ts
  • apps/pythinker-code/src/migration/migration-screen.ts
  • apps/pythinker-code/src/tui/components/dialogs/session-picker.ts
  • apps/pythinker-code/src/tui/presentation/migration-phase-model.ts
  • apps/pythinker-code/src/tui/pythinker-tui.ts
  • apps/pythinker-code/test/cli/options.test.ts
  • apps/pythinker-code/test/cli/run-shell.test.ts
  • apps/pythinker-code/test/cli/session-flag-picker.test.ts
  • apps/pythinker-code/test/migration/badge.test.ts
  • apps/pythinker-code/test/migration/command.test.ts
  • apps/pythinker-code/test/migration/detect-pending.test.ts
  • apps/pythinker-code/test/migration/migration-screen.test.ts
  • apps/pythinker-code/test/tui/components/dialogs/session-picker.test.ts
  • apps/pythinker-code/test/tui/parity/feature-matrix.ts
  • apps/pythinker-code/test/tui/presentation/migration-phase-model.test.ts
  • apps/pythinker-code/test/tui/pythinker-tui-startup.test.ts
  • apps/pythinker-web/index.html
  • apps/pythinker-web/src/App.vue
  • apps/pythinker-web/src/api/daemon/mappers.ts
  • apps/pythinker-web/src/api/daemon/wire.ts
  • apps/pythinker-web/src/api/types.ts
  • apps/pythinker-web/src/components/ChatDock.vue
  • apps/pythinker-web/src/components/ChatPane.vue
  • apps/pythinker-web/src/components/Composer.vue
  • apps/pythinker-web/src/components/ConversationPane.vue
  • apps/pythinker-web/src/components/MascotSprite.vue
  • apps/pythinker-web/src/components/NewSessionDialog.vue
  • apps/pythinker-web/src/components/PythinkerLogo.vue
  • apps/pythinker-web/src/components/Sidebar.vue
  • apps/pythinker-web/src/composables/useApngRestart.ts
  • apps/pythinker-web/src/composables/usePythinkerWebClient.ts
  • apps/pythinker-web/src/env.d.ts
  • apps/pythinker-web/src/i18n/locales/en/composer.ts
  • apps/pythinker-web/src/i18n/locales/en/conversation.ts
  • apps/pythinker-web/src/i18n/locales/en/settings.ts
  • apps/pythinker-web/src/i18n/locales/en/status.ts
  • apps/pythinker-web/src/lib/modelThinking.ts
  • apps/pythinker-web/src/style.css
  • apps/pythinker-web/src/styles/design-platform.css
  • apps/pythinker-web/src/styles/scrollbar.css
  • apps/pythinker-web/src/styles/shiki.css
  • apps/pythinker-web/test/composer.test.ts
  • apps/pythinker-web/test/conversation-pane-anchors.test.ts
  • apps/pythinker-web/test/conversation-pane-follow.test.ts
  • apps/pythinker-web/test/design-tokens.test.ts
  • apps/pythinker-web/test/mascot-restart.test.ts
  • apps/pythinker-web/test/mascot-sprite.test.ts
  • apps/pythinker-web/test/model-mappers.test.ts
  • apps/pythinker-web/test/model-thinking.test.ts
  • apps/pythinker-web/test/set-model-rollback.test.ts
  • apps/pythinker-web/test/sidebar.test.ts
  • apps/pythinker-web/test/theme-contrast.test.ts
  • apps/pythinker-web/test/windows-titlebar.test.ts
  • apps/site/.gitignore
  • apps/site/Caddyfile
  • apps/site/Dockerfile
  • apps/site/index.html
  • apps/site/package.json
  • apps/site/public/_headers
  • apps/site/public/code/oauth-success.html
  • apps/site/public/index.md
  • apps/site/public/robots.txt
  • apps/site/public/sitemap.xml
  • apps/site/scripts/build-cdn.mjs
  • apps/site/src/App.vue
  • apps/site/src/components/AgentLoop.vue
  • apps/site/src/components/InstallCommand.vue
  • apps/site/src/components/LegacyDownloadsPopup.vue
  • apps/site/src/components/ParticleField.vue
  • apps/site/src/components/PythinkerMascot.vue
  • apps/site/src/install-channels.js
  • apps/site/src/main.js
  • apps/site/src/style.css
  • apps/site/src/webmcp.js
  • apps/site/vite.config.js
  • docs/.vitepress/config.ts
  • docs/AGENTS.md
  • docs/guides/desktop.md
  • docs/guides/getting-started.md
  • docs/guides/migration.md
  • docs/reference/pythinker-command.md
  • docs/reference/tools.md
  • flake.nix
  • packages/agent-core/src/profile/default.ts
  • packages/agent-core/src/profile/default/general.md
  • packages/agent-core/src/profile/default/general.yaml
  • packages/agent-core/src/rpc/core-impl.ts
  • packages/agent-core/src/services/modelCatalog/modelCatalog.ts
  • packages/agent-core/src/session/index.ts
  • packages/agent-core/src/tools/builtin/collaboration/dynamic-workflow.md
  • packages/agent-core/src/tools/builtin/collaboration/dynamic-workflow.ts
  • packages/agent-core/src/tools/builtin/state/todo-list.md
  • packages/agent-core/src/tools/builtin/state/todo-list.ts
  • packages/agent-core/test/profile/general-mode.test.ts
  • packages/agent-core/test/prompt-placeholders.test.ts
  • packages/agent-core/test/services/model-catalog-service.test.ts
  • packages/agent-core/test/tools/builtin-current.test.ts
  • packages/agent-core/test/tools/todo-list.test.ts
  • packages/migration-legacy/CHANGELOG.md
  • packages/migration-legacy/package.json
  • packages/migration-legacy/src/atomic-write.ts
  • packages/migration-legacy/src/detect.ts
  • packages/migration-legacy/src/index.ts
  • packages/migration-legacy/src/marker.ts
  • packages/migration-legacy/src/migration-errors-log.ts
  • packages/migration-legacy/src/paths.ts
  • packages/migration-legacy/src/prompt.ts
  • packages/migration-legacy/src/pythinker-cli-schema.ts
  • packages/migration-legacy/src/report.ts
  • packages/migration-legacy/src/run-migration.ts
  • packages/migration-legacy/src/session-index.ts
  • packages/migration-legacy/src/sessions/classify.ts
  • packages/migration-legacy/src/sessions/close-tool-calls.ts
  • packages/migration-legacy/src/sessions/content-part.ts
  • packages/migration-legacy/src/sessions/index.ts
  • packages/migration-legacy/src/sessions/migrate-one.ts
  • packages/migration-legacy/src/sessions/state-writer.ts
  • packages/migration-legacy/src/sessions/translator.ts
  • packages/migration-legacy/src/sessions/wire-writer.ts
  • packages/migration-legacy/src/sessions/workdir-bucket.ts
  • packages/migration-legacy/src/steps/config.ts
  • packages/migration-legacy/src/steps/mcp.ts
  • packages/migration-legacy/src/steps/skills.ts
  • packages/migration-legacy/src/steps/user-history.ts
  • packages/migration-legacy/src/stub-detect.ts
  • packages/migration-legacy/src/types.ts
  • packages/migration-legacy/test/.gitkeep
  • packages/migration-legacy/test/atomic-write.test.ts
  • packages/migration-legacy/test/detect.test.ts
  • packages/migration-legacy/test/fixtures/archived/context.jsonl
  • packages/migration-legacy/test/fixtures/archived/state.json
  • packages/migration-legacy/test/fixtures/archived/wire.jsonl
  • packages/migration-legacy/test/fixtures/broken-state-json/context.jsonl
  • packages/migration-legacy/test/fixtures/broken-state-json/state.json
  • packages/migration-legacy/test/fixtures/broken-state-json/wire.jsonl
  • packages/migration-legacy/test/fixtures/large-100msgs/context.jsonl
  • packages/migration-legacy/test/fixtures/large-100msgs/state.json
  • packages/migration-legacy/test/fixtures/large-100msgs/wire.jsonl
  • packages/migration-legacy/test/fixtures/legacy-protocol-1.3/context.jsonl
  • packages/migration-legacy/test/fixtures/legacy-protocol-1.3/state.json
  • packages/migration-legacy/test/fixtures/legacy-protocol-1.3/wire.jsonl
  • packages/migration-legacy/test/fixtures/multi-workdir/.pythinker/pythinker.json
  • packages/migration-legacy/test/fixtures/multi-workdir/.pythinker/sessions/73d3cf4d1cf337adf57d6732b8be2205/uuid-a1/context.jsonl
  • packages/migration-legacy/test/fixtures/multi-workdir/.pythinker/sessions/73d3cf4d1cf337adf57d6732b8be2205/uuid-a1/state.json
  • packages/migration-legacy/test/fixtures/multi-workdir/.pythinker/sessions/73d3cf4d1cf337adf57d6732b8be2205/uuid-a2/context.jsonl
  • packages/migration-legacy/test/fixtures/multi-workdir/.pythinker/sessions/73d3cf4d1cf337adf57d6732b8be2205/uuid-a2/state.json
  • packages/migration-legacy/test/fixtures/multi-workdir/.pythinker/sessions/dbf62706c1b976e79a5e7cfcc3491a1f/uuid-b1/test
  • packages/migration-legacy/test/fixtures/multi-workdir/.pythinker/sessions/kaos-foo_caaf674614e26c6365945408f85c9c9a/uuid-c1/context.jsonl
  • packages/migration-legacy/test/fixtures/multi-workdir/.pythinker/sessions/kaos-foo_caaf674614e26c6365945408f85c9c9a/uuid-c1/state.json
  • packages/migration-legacy/test/fixtures/recent-protocol-1.10/context.jsonl
  • packages/migration-legacy/test/fixtures/recent-protocol-1.10/state.json
  • packages/migration-legacy/test/fixtures/recent-protocol-1.10/wire.jsonl
  • packages/migration-legacy/test/fixtures/tiny-hello-world/context.jsonl
  • packages/migration-legacy/test/fixtures/tiny-hello-world/state.json
  • packages/migration-legacy/test/fixtures/tiny-hello-world/wire.jsonl
  • packages/migration-legacy/test/fixtures/with-image/context.jsonl
  • packages/migration-legacy/test/fixtures/with-image/wire.jsonl
  • packages/migration-legacy/test/fixtures/with-subagent-collapsed/context.jsonl
  • packages/migration-legacy/test/fixtures/with-subagent-collapsed/state.json
  • packages/migration-legacy/test/fixtures/with-subagent-collapsed/wire.jsonl
  • packages/migration-legacy/test/fixtures/with-thinking/context.jsonl
  • packages/migration-legacy/test/fixtures/with-thinking/state.json
  • packages/migration-legacy/test/fixtures/with-thinking/wire.jsonl
  • packages/migration-legacy/test/fixtures/with-tool-calls/context.jsonl
  • packages/migration-legacy/test/fixtures/with-tool-calls/state.json
  • packages/migration-legacy/test/fixtures/with-tool-calls/wire.jsonl
  • packages/migration-legacy/test/integration.test.ts
  • packages/migration-legacy/test/marker.test.ts
  • packages/migration-legacy/test/migration-errors-log.test.ts
  • packages/migration-legacy/test/paths.test.ts
  • packages/migration-legacy/test/prompt-modules.d.ts
  • packages/migration-legacy/test/prompt.test.ts
  • packages/migration-legacy/test/pythinker-cli-schema.test.ts
  • packages/migration-legacy/test/report.test.ts
  • packages/migration-legacy/test/resume.integration.test.ts
  • packages/migration-legacy/test/session-index.test.ts
  • packages/migration-legacy/test/sessions/classify.test.ts
  • packages/migration-legacy/test/sessions/close-tool-calls.test.ts
  • packages/migration-legacy/test/sessions/content-part.test.ts
  • packages/migration-legacy/test/sessions/fixtures.snapshot.test.ts
  • packages/migration-legacy/test/sessions/migrate-one.test.ts
  • packages/migration-legacy/test/sessions/sessions-step.test.ts
  • packages/migration-legacy/test/sessions/state-writer.test.ts
  • packages/migration-legacy/test/sessions/translator.test.ts
  • packages/migration-legacy/test/sessions/wire-writer.test.ts
  • packages/migration-legacy/test/sessions/workdir-bucket.test.ts
  • packages/migration-legacy/test/steps/config.test.ts
  • packages/migration-legacy/test/steps/mcp.test.ts
  • packages/migration-legacy/test/steps/skills.test.ts
  • packages/migration-legacy/test/steps/user-history.test.ts
  • packages/migration-legacy/test/stub-detect.test.ts
  • packages/migration-legacy/tsconfig.json
  • packages/migration-legacy/tsdown.config.ts
  • packages/migration-legacy/vitest.config.ts
  • packages/oauth/src/oauth-pages.ts
  • packages/protocol/src/__tests__/model-catalog.test.ts
  • packages/protocol/src/__tests__/session.test.ts
  • packages/protocol/src/modelCatalog.ts
  • packages/protocol/src/session.ts
  • packages/pythinker-migration-legacy/package.json
  • packages/server-e2e/scripts/run-docker-e2e.sh

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Aug 17, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@pymodel/pythinker-code@d441c63
npx https://pkg.pr.new/@pymodel/pythinker-code@d441c63

commit: d441c63

@elkaix
elkaix merged commit 7dd68cb into main Aug 17, 2026
12 checks passed
@elkaix
elkaix deleted the fix/desktop-release-changesets branch August 17, 2026 00:53
elkaix added a commit that referenced this pull request Aug 17, 2026
## Related Issue

No issue. The problem is described below.

## Problem

The web model quick-switch menu opens above its pill with a fixed
`160px`
minimum height. When the pill sits near the top of the viewport, the
menu
reached past the viewport edge, so its upper entries could not be
scrolled to.
The existing test asserted the `160px` value, so it encoded the
off-screen
behaviour rather than catching it.

## What changed

Clamp the menu height to the space actually available above the pill.
The
`360px` cap is unchanged. The low-space case in the test now asserts the
constrained height.

This pull request was originally a larger desktop and web branch. #97
landed
that work, so only this fix is left; the branch has been reset onto
`main`.

## Checklist

- [x] I have read the
[CONTRIBUTING](https://github.com/PyModel/pythinker-code/blob/main/CONTRIBUTING.md)
document.
- [x] I have linked a related issue, or explained the problem above.
- [x] I have added tests that prove my feature works.
- [x] Ran `gen-changesets` skill, or this PR needs no changeset.
- [x] Ran `gen-docs` skill, or this PR needs no doc update.
elkaix pushed a commit that referenced this pull request Aug 17, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @pymodel/pythinker-code@0.20.0

### Minor Changes

- [#97](#97)
[`7dd68cb`](7dd68cb)
- Remove the legacy pythinker-cli migration: the `pythinker migrate`
subcommand, the first-launch
  migration prompt, and the `[imported]` session badge.

### Patch Changes

- [#97](#97)
[`7dd68cb`](7dd68cb)
- Make the workspace header, session timestamps, and the settings row
legible in dark mode on the translucent desktop sidebar.

- [#96](#96)
[`71c51b8`](71c51b8)
- Keep the web model quick-switch menu inside the viewport when the
composer sits near the top of the window.
## @pymodel/pythinker-desktop@0.1.3

### Patch Changes

- [#97](#97)
[`7dd68cb`](7dd68cb)
- Make the workspace header, session timestamps, and the settings row
legible in dark mode on the translucent desktop sidebar.

- [#97](#97)
[`7dd68cb`](7dd68cb)
- Publish desktop releases to a dedicated update channel so update
checks resolve a desktop build instead of an unrelated release, and fail
the release when a packaged build carries no update feed.

- [#97](#97)
[`7dd68cb`](7dd68cb)
- Pin the Host port so the desktop app reconnects to its own Host, and
stop reporting builds that cannot self-update as update errors.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
elkaix added a commit that referenced this pull request Aug 17, 2026
…#100)

## Related Issue

No issue — this continues the settings/desktop UI work that landed in
#97, and the problem is described below.

## Problem

Settings was a modal. `SettingsDialog.vue` had grown to 1,571 lines
fusing four unrelated jobs — the fixed backdrop and focus trap, its own
tab navigation, ten page bodies, and the derived state each page needed
— behind an interface of 22 props and 12 emits. Because the modal chrome
was welded to the content, the pages could only ever render as an
overlay: opening Settings hid the whole app behind a scrim, including
the sidebar the user had just been navigating.

Three smaller problems came with it:

- The settings surface only exposed appearance, account and connection.
Plugins, skills, subagents, hooks and usage had no UI at all, and
plugins and subagent profiles had no REST surface either — they existed
only over the internal core RPC.
- A disabled skill could not be turned off from anywhere.
- The README had no desktop download badge, and the desktop artifacts
are published to a different repository than the one the README lives
in.

## What changed

**Settings became a route.** `SettingsDialog.vue` is deleted and
replaced by focused modules:

- `composables/useSettingsNav.ts` owns the `SettingsTab` union, the
three nav groups, the active tab, and the guarded lazy loads. Counts
come in as `MaybeRefOrGetter` so it reads live list lengths instead of
duplicating state.
- `components/settings/SettingsNav.vue` renders the nav and is hosted by
the real `Sidebar`.
- `components/settings/SettingsPane.vue` hosts the pages inline in the
content area. No backdrop, nothing `position: fixed`.
- Ten page components under `components/settings/pages/`, each taking
only the props it needs and owning its own derived state.
- `components/settings/ListingRow.vue` and `settings.css` carry the
shared row and rules.

`Sidebar` gained `mode: 'sessions' | 'settings'`. Its header and New
Session button stay mounted in both modes; only the body below swaps.
`ConversationPane` yields the content area to `SettingsPane`. Mobile is
untouched — it stays a sheet.

**Five new settings pages**, plus the backend they needed:

- Skills, with an enable/disable switch. Turning one off writes a
`disabledSkills` config list that `SessionSkillRegistry.register`
filters. That is the single funnel every skill source goes through, so a
disabled skill is invisible to the model, the slash menu and the API
alike — built-ins included.
- Plugins, Subagents, Connectors and Hooks.
- Usage stats: token, session, turn and cost totals with a per-model
share bar.
- `ICatalogService` behind `GET /plugins`, `POST
/plugins/{id}:set-enabled` and `GET /agent-profiles`, with protocol
schemas for each. One service rather than two, because both collections
are global rather than session-scoped.

**Row design** follows a flat, borderless idiom: a leading glyph, the
name and its tags inline, and a right-hand action cluster. Only the row
content fades when an entry is off, so the switch that turns it back on
keeps full contrast.

**Model menu** splits into a model list and an effort list, with the
effort levels derived from the model's declared `supportEfforts`.

**README** gains three desktop download badges in the hero row.
Per-platform counts cannot come from shields.io directly: its asset
wildcards silently report `0` (a tag whose `.dmg` had 39,877 downloads
returns `0` for `*.dmg`), exact asset names embed the version so they
break on every release, and `dynamic/json` rejects every filter
expression. A daily workflow sums the counts from the desktop releases
repo and publishes them as shields `endpoint` documents on the orphan
`badges` branch, which `main`'s protection rules do not block.

### Verification

- `pnpm -C apps/pythinker-web exec vitest run` — 322 passed (58 files)
- `pnpm -C apps/pythinker-web exec vue-tsc --noEmit` — clean
- `pnpm run lint` — 0 errors
- `pnpm run build` — clean
- agent-core, protocol and server suites — green

Every new assertion was proven able to fail by breaking the
implementation it covers and confirming the red before restoring.

### Note for reviewers

The active settings tab now persists across close and reopen. The modal
remounted on every open and reset to General; the composable lives in
`App.vue` and does not. This is a deliberate behaviour change, called
out here rather than buried.

## Checklist

- [x] I have read the
[CONTRIBUTING](https://github.com/PyModel/pythinker-code/blob/main/CONTRIBUTING.md)
document.
- [x] I have linked a related issue, or explained the problem above.
- [x] I have added tests that prove my feature works.
- [x] Ran `gen-changesets` skill, or this PR needs no changeset. — two
`minor` changesets, both listing `@pymodel/pythinker-code`: the changed
packages are all ignored by changesets, but the web app and the server
both ship inside the CLI release artifact, so the CLI is the package
that carries them.
- [x] Ran `gen-docs` skill, or this PR needs no doc update.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added a routed settings experience for agents, plugins, skills,
subagents, connectors, hooks, usage, account, and advanced options.
- Added plugin enable/disable controls, connector restart actions,
catalog listings, and skill management.
  - Added support for disabling selected skills.
  - Improved model selection with separate thinking-effort navigation.
  - Added desktop download badges for macOS and Windows releases.

- **Improvements**
- Improved dock panel positioning while keeping the message composer
fixed.
- Added clearer loading, empty, status, and error states throughout
settings.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant