Commit Graph

326 Commits

Author SHA1 Message Date
ben
2e440d4721 Task/react port cutover react only workspace fixes (#1470)
* scaffold(react): add parallel React entry and app-root shell

Introduces apps/app/src/index.react.tsx and apps/app/src/react-app/shell/app-root.tsx as the base for a component-by-component migration, while keeping the Solid runtime as the shipped default. Updates vite.config so files inside src/react-app/ and the new React entry are routed through the React plugin rather than Solid.

Made-with: Cursor

* feat(react-app/kernel): port platform abstraction to React

Adds react-app/kernel/platform.tsx with a React context equivalent of the Solid PlatformProvider and a createDefaultPlatform() helper that mirrors the Tauri-vs-web bootstrap logic from src/index.tsx. Wires it into the new React entry so downstream hooks can depend on usePlatform() during the migration.

Made-with: Cursor

* feat(react-app/infra): port React Query client singleton

Copies the shared TanStack Query client helper into react-app/infra/query-client.ts and wires QueryClientProvider into the new React entry so future domain modules can share one Query cache.

Made-with: Cursor

* feat(react-app/kernel): add minimal Zustand store for React migration

Introduces react-app/kernel/store.ts with a small OpenworkStore covering bootstrap, server, workspaces, selected session, and an error banner. This gives downstream domain modules a single place to read and mutate React-owned app state as the migration proceeds; it will grow as later phases port more behavior.

Made-with: Cursor

* feat(react-app/kernel): add selectors module

Adds react-app/kernel/selectors.ts with a handful of small selectors (active workspace, server status, server URL, error banner) so domain components can consume store state through named selectors instead of reaching into the store shape directly.

Made-with: Cursor

* feat(react-app/kernel): port ServerProvider to React

Adds react-app/kernel/server-provider.tsx with a React context equivalent of context/server.tsx: persisted server list, active URL, health polling, and add/remove/setActive helpers. Health checks reuse the Tauri or fetch transport based on runtime, and hosted web deployments skip any persisted localhost target.

Made-with: Cursor

* feat(react-app/kernel): port GlobalSDKProvider to React

Adds react-app/kernel/global-sdk-provider.tsx mirroring context/global-sdk.tsx: builds an OpenCode client keyed to the active server URL, subscribes to the SSE event stream when healthy, and fans events into a lightweight channel emitter. Replaces the Solid @solid-primitives/event-bus with a small React-friendly emitter so downstream React domains can listen without pulling in solid-js runtime.

Made-with: Cursor

* feat(react-app/kernel): port GlobalSyncProvider to React

Adds react-app/kernel/global-sync-provider.tsx with a React context equivalent of context/global-sync.tsx. Uses a single useState-backed GlobalState plus a workspaces map for per-directory slices, and re-implements the refresh/refreshDirectory helpers and the directory-scoped event subscriptions on top of the new GlobalEventEmitter from global-sdk-provider.

Made-with: Cursor

* feat(react-app/kernel): port LocalProvider to React

Adds react-app/kernel/local-provider.tsx as a React-side persisted UI state + preferences provider equivalent to context/local.tsx. Uses plain useState with localStorage serialisation and migrates the legacy standalone thinking preference into the unified preferences shape.

Made-with: Cursor

* feat(react-app/shell): compose full provider stack in React entry

Adds react-app/shell/providers.tsx that wraps ServerProvider, GlobalSDKProvider, GlobalSyncProvider, and LocalProvider in the same order as the Solid entry, and mounts the default server URL resolution logic from the original src/index.tsx. Wires the new AppProviders into the React entry so the migration now runs behind the full kernel provider stack.

Made-with: Cursor

* feat(react-app/shell): port startup deep-link bridge

Adds react-app/shell/startup-deep-links.ts that mirrors the Tauri and web deep-link startup behaviour from src/index.tsx, and calls it from the React entry alongside the existing deployment dataset hook.

Made-with: Cursor

* feat(react-app/shell): mount HashRouter in Tauri and BrowserRouter on web

Pulls react-router-dom into apps/app and selects HashRouter for Tauri builds (matching the Solid shell behaviour) and BrowserRouter elsewhere. Wraps AppRoot in the chosen router so downstream pages can use route primitives from day one of the React migration.

Made-with: Cursor

* feat(react-app/kernel): port reload + reset system state to React hook

Adds react-app/kernel/system-state.ts with a useSystemState() hook that covers the reload-pending state (reasons, trigger, mark/clear helpers) and the reset modal state (open/close, confirm flow that clears localStorage and relaunches). Cache repair, Docker cleanup, and the Tauri updater surface stay in the original Solid factory for now and will be ported as the settings shell migrates.

Made-with: Cursor

* feat(react-app/kernel): port core model-config helpers and default model hook

Adds react-app/kernel/model-config.ts with the framework-agnostic parse/serialize helpers for session-level and workspace-level model choices, plus a lightweight useDefaultModel() hook that reads and persists the default model ref to localStorage. The richer workspace-scoped overrides, auto-compact context, and model picker orchestration stay in the Solid factory for now and will be ported when the session + settings surfaces need them.

Made-with: Cursor

* feat(react-app/session): port ModelPickerModal to React

Adds react-app/domains/session/modals/model-picker-modal.tsx as a faithful 1:1 React port of src/app/components/model-picker-modal.tsx. Preserves the recommended / connected / more-providers sections, search, keyboard navigation (arrow keys, enter, escape), behaviour chip row on the active option, and the empty/"no results" state. Uses an inline ProviderIcon placeholder until the full Solid ProviderIcon is ported. Installs lucide-react so the React tree can consume the same icon set.

Made-with: Cursor

* refactor(react-app/session): move session-sync + usechat-adapter into domains/session/sync

Relocates the session sync store and the AI SDK chat transport adapter into react-app/domains/session/sync/, with re-export shims left at the old react/session/ paths for Solid consumers still importing from there. These two files compile as a pair so they are moved together; the shims will disappear once the session route is mounted from react-app.

Made-with: Cursor

* refactor(react-app/session): move transition-controller into domains/session/sync

Pure transition/render-model helper moves from src/react/session/ into react-app/domains/session/sync/. Leaves a re-export shim at the old path so session-surface.react.tsx and debug-panel.react.tsx can keep their imports until they migrate too.

Made-with: Cursor

* refactor(react-app/session): move runtime-sync into domains/session/sync

React effect component that keeps the workspace session sync loop alive now lives under react-app/domains/session/sync/runtime-sync.tsx. Existing callers import through the shim at src/react/session/runtime-sync.react.tsx until Solid mounts move off it.

Made-with: Cursor

* refactor(react-app/session): move markdown block into domains/session/surface

Relocates the markdown/streamdown renderer to react-app/domains/session/surface/markdown.tsx with a re-export shim at the old react/session/ path.

Made-with: Cursor

* refactor(react-app/session): move tool-call view into domains/session/surface

Ports the ToolCallView component file into react-app/domains/session/surface/tool-call.tsx and leaves a shim at src/react/session/tool-call.react.tsx for the Solid transcript to keep importing from.

Made-with: Cursor

* refactor(react-app/session): move message-list into domains/session/surface

Relocates the SessionTranscript renderer to react-app/domains/session/surface/message-list.tsx and retargets its local imports to the already-moved markdown and tool-call modules. Retains a shim at src/react/session/message-list.react.tsx for Solid session-surface.

Made-with: Cursor

* refactor(react-app/session): move debug-panel into domains/session/surface

Session debug overlay moves to react-app/domains/session/surface/debug-panel.tsx with imports retargeted to the already-moved transition-controller and the shared openwork-server types. Leaves a shim at src/react/session/debug-panel.react.tsx for session-surface.

Made-with: Cursor

* refactor(react-app/session): move composer notice into domains/session/surface/composer

Made-with: Cursor

* refactor(react-app/session): move Lexical prompt editor into domains/session/surface/composer

Made-with: Cursor

* refactor(react-app/session): move ReactSessionComposer into domains/session/surface/composer

Made-with: Cursor

* refactor(react-app/session): move SessionSurface into domains/session/surface

Core React session surface (transcript + composer + sync bridge) moves to react-app/domains/session/surface/session-surface.tsx. Imports update to point into the relocated surface, sync, and composer modules, plus the shared infra query client. Shim at src/react/session/session-surface.react.tsx keeps Solid pages/app.tsx compiling against the React island pattern.

Made-with: Cursor

* feat(react-app/workspace): re-export shared modal style tokens

Creates react-app/domains/workspace/modal-styles.ts as a re-export of the framework-agnostic class-name constants in src/app/workspace/modal-styles.ts, so React-side workspace modals consume a domain-scoped import path while the Solid tree keeps using the original file.

Made-with: Cursor

* feat(react-app/workspace): re-export workspace types

Creates react-app/domains/workspace/types.ts as a re-export so React-side modal flows pull workspace types through the domain path.

Made-with: Cursor

* feat(react-app/workspace): port WorkspaceOptionCard to React

Adds react-app/domains/workspace/option-card.tsx as a 1:1 React port of app/workspace/option-card.tsx, using lucide-react's ChevronRight and the shared modal style tokens. Accepts an icon as a React ComponentType so callers can pass any lucide-react icon directly.

Made-with: Cursor

* feat(react-app/workspace): port RemoteWorkspaceFields to React

Ports the reusable remote-worker form (URL, token show/hide, optional directory, display name) into react-app/domains/workspace/remote-workspace-fields.tsx, keeping the Solid "Show" conditional as a plain conditional render and reusing the shared modal style tokens.

Made-with: Cursor

* feat(react-app/workspace): port CreateWorkspaceLocalPanel to React

Faithful 1:1 React port of the Solid local-create panel: folder picker, team template grid, sandbox progress stepper with logs, worker-setup warning banner, and confirm/cancel footer. Uses lucide-react icons and the shared modal style tokens.

Made-with: Cursor

* feat(react-app/workspace): port CreateWorkspaceSharedPanel to React

Ports the signed-in vs signed-out cloud workers panel, org selector, search input, worker cards with status badges, and "open cloud dashboard" footer into react-app/domains/workspace/create-workspace-shared-panel.tsx. Uses lucide-react icons and keeps behaviour parity with the Solid source.

Made-with: Cursor

* feat(react-app/workspace): port CreateRemoteWorkspaceModal to React

Ports the standalone remote-connect modal (host URL, token show/hide, optional directory/display name, cancel/confirm footer, optional inline render) to react-app/domains/workspace/create-remote-workspace-modal.tsx using the already-ported RemoteWorkspaceFields and the shared modal style tokens.

Made-with: Cursor

* feat(react-app/workspace): port CreateWorkspaceModal orchestrator to React

Ports the full create-workspace modal state machine to react-app/domains/workspace/create-workspace-modal.tsx. Covers the chooser/local/remote/shared screens, Den cloud org + worker list with live search + tokens handoff, template cache bootstrap, progress elapsed tracker, and focus/escape lifecycle. Reuses the already-ported local panel, shared panel, remote fields, and option card; depends on the React PlatformProvider for desktop-vs-web deep-link behaviour.

Made-with: Cursor

* feat(react-app/workspace): port ShareWorkspaceAccessPanel to React

Ports the access panel for the share workspace modal into react-app/domains/workspace/share-workspace-access-panel.tsx. Preserves the remote-access toggle with pending/save state, credential fields with reveal + copy, optional collaborator expansion, messaging setup card, warning banner, and note line. Uses lucide-react icons and the shared modal style tokens.

Made-with: Cursor

* feat(react-app/workspace): port ShareWorkspaceTemplatePanel to React

Ports the full template-sharing flow (chooser / public link / team save) into react-app/domains/workspace/share-workspace-template-panel.tsx. Preserves the sensitive-config warning with include/exclude toggles, generated-link copy/regenerate UX, sign-in-required fallback for the team save, and the 'Included in this template' summary card.

Made-with: Cursor

* feat(react-app/workspace): port ShareWorkspaceModal orchestrator to React

Ports the modal chrome + chooser + view switcher for the share workspace flow into react-app/domains/workspace/share-workspace-modal.tsx. Handles the chooser/template/template-public/template-team/access screens, escape-key back navigation, clipboard copy with a 2s confirmation, and syncs remoteAccess toggle state with the parent across opens.

Made-with: Cursor

* feat(react-app/settings): port app-settings state modules to React

Adds React equivalents of the app-settings state surface under react-app/domains/settings/state/:
- model-controls-store.ts: plain ModelControlsStore shape (no Solid accessors)
- model-controls-provider.tsx: React context + useModelControls() hook
- session-display-preferences.ts: show-thinking toggle backed by the kernel LocalProvider
- feature-flags-preferences.ts: microsandbox-create feature flag toggle backed by LocalProvider

Made-with: Cursor

* feat(react-app/shell-feedback): port StatusToast to React

Adds react-app/domains/shell-feedback/status-toast.tsx mirroring the Solid status toast: tone-specific icon tile (success/info/warning/error), title + optional description, optional action + dismiss buttons, and slide-in-from-top entrance animation.

Made-with: Cursor

* feat(react-app/design-system): port ConfirmModal primitive

Adds react-app/design-system/modals/confirm-modal.tsx as a reusable confirm/cancel dialog with warning/danger variants, replacing the Solid Button dependency with inline primary/danger/outline button classes so the React tree can consume this modal without a ported design-system button yet.

Made-with: Cursor

* feat(react-app/workspace): port RenameWorkspaceModal to React

Ports the simple rename-workspace dialog into react-app/domains/workspace/rename-workspace-modal.tsx, focus-on-open behaviour preserved and Enter-to-save keyboard shortcut kept. Uses shared modal-styles pill buttons and input class instead of the Solid Button/TextInput components.

Made-with: Cursor

* feat(react-app/session): port RenameSessionModal to React

Adds react-app/domains/session/modals/rename-session-modal.tsx for the session rename dialog. Parallels the workspace rename modal port: auto-focus on open, enter-to-save, i18n strings, shared modal-styles buttons and input class.

Made-with: Cursor

* feat(react-app/settings): port ResetModal to React

Ports the destructive reset confirmation dialog (typing RESET to enable) into react-app/domains/settings/modals/reset-modal.tsx. Keeps the onboarding-vs-all mode split, active-runs warning, and danger-styled confirm button.

Made-with: Cursor

* feat(react-app/shell-feedback): port ReloadWorkspaceToast to React

Adds react-app/domains/shell-feedback/reload-workspace-toast.tsx with the Solid toast's full copy logic for skill/plugin/mcp/config/agent/command triggers, active-tasks warning, blocked-reason display, and danger-vs-primary reload button depending on active runs.

Made-with: Cursor

* feat(react-app/design-system): port Button and TextInput primitives

Adds react-app/design-system/{button.tsx,text-input.tsx}. Button covers the five variants (primary/secondary/ghost/outline/danger) used across the Solid app. TextInput wraps the standard input with an optional label+hint pair. Both forward refs.

Made-with: Cursor

* feat(react-app/design-system): port FlyoutItem and ProviderIcon primitives

- FlyoutItem: copy-to-toast style animated flyout that flies from origin to target rect.
- ProviderIcon: SVG icons for openai/anthropic/opencode plus initial-letter fallback for other providers.

Made-with: Cursor

* feat(react-app/design-system): port SelectMenu primitive

Adds react-app/design-system/select-menu.tsx as a React port of the Solid select menu. Keeps the trigger + dropdown, chevron rotation, outside-click and Escape closing behaviour, and accessible listbox/options semantics.

Made-with: Cursor

* feat(react-app/design-system): port WebUnavailableSurface

Wraps children with a dismissable web-only banner and an inert overlay when a feature is web-unavailable. Ports the Solid implementation to React without the Solid-only classList attribute.

Made-with: Cursor

* feat(react-app/bundles): re-export framework-agnostic bundle helpers

Adds thin re-export modules under react-app/domains/bundles/ for the Solid-free bundle helpers (types, schema, url-policy, sources, apply, publish, skill-org-publish, index). React consumers will now import bundle behaviour through the react-app domain path while the Solid tree keeps using the original files.

Made-with: Cursor

* feat(react-app/connections): port AddMcpModal to React

Ports the add-MCP dialog with remote/local server type toggle, OAuth opt-in checkbox for remote servers, command split for local servers, validation errors, and remote-workspace disable hint. Reuses the React design-system Button/TextInput primitives.

Made-with: Cursor

* feat(react-app/session): port StatusBar to React

Ports the bottom status bar to react-app/domains/session/chat/status-bar.tsx. The React version receives the MCP-connected count as a prop instead of pulling it from a connections context, since the React connections provider is not ported yet. All other behaviour (ready/limited/disconnected copy, docs/feedback/settings buttons, optional custom status override) matches the Solid source.

Made-with: Cursor

* feat(react-app/session): port QuestionModal to React

Ports the multi-question wizard modal with keyboard navigation (arrow keys + enter), single/multiple/custom answer modes, question counter, and auto-advance on single-option selection. Uses the React design-system Button for the Next/Submit CTA.

Made-with: Cursor

* feat(react-app/connections): port ControlChromeSetupModal to React

Adds react-app/domains/connections/modals/control-chrome-setup-modal.tsx with the three-step setup card, use-existing-profile toggle, contextual hint below the toggle, and connect/save CTA that switches label based on mode and shows a busy spinner.

Made-with: Cursor

* feat(react-app/bundles): port BundleStartModal to React

Adds react-app/domains/bundles/start-modal.tsx for the template start flow: template name + description header, items chip row (with +N more overflow), folder picker card, cancel/create-workspace footer, and Escape-to-close behaviour.

Made-with: Cursor

* feat(react-app/bundles): port BundleImportModal to React

Adds react-app/domains/bundles/import-modal.tsx for the import-bundle flow: header + included items chips, Create-new-worker CTA, expandable existing-worker picker with status badges, disabled-reason hint, current-worker emphasis, and Escape-to-close.

Made-with: Cursor

* feat(react-app/bundles): port SkillDestinationModal to React

Adds react-app/domains/bundles/skill-destination-modal.tsx for the pick-a-workspace-for-this-skill flow. Preserves the skill summary header (with trigger chip), workspace list with colour-coded circles and current/sandbox/remote badges, busy indicator per workspace, optional create-worker / connect-remote CTAs, and sticky footer with the selected-workspace summary + submit button.

Made-with: Cursor

* feat(react-app/shell-feedback): port status-toasts store + TopRightNotifications

Adds StatusToastsProvider + useStatusToasts hook + StatusToastsViewport with auto-dismiss (3.2s info / 4.2s warning+error) and a rolling 4-item cap, plus the top-right notifications column that stacks the reload toast above the status toasts. Mirrors the Solid shell behaviour so downstream React pages can consume the same toast surface.

Made-with: Cursor

* feat(react-app/settings): port PluginsView to React

Ports the plugins settings page into react-app/domains/settings/pages/plugins-view.tsx. Inverts the Solid useExtensions() context dependency: the React PluginsView receives its extensions store as a prop (PluginsExtensionsStore) so the full extensions provider can be ported later without blocking this page. All UI, scope switcher, suggested-plugins grid, guided steps, installed list, and custom add input are preserved.

Made-with: Cursor

* feat(react-app/settings): port ConfigView to React

Ports the advanced-tab config view (OpenWork server URL/token form, test connection, engine reload card, hostInfo tokens card with copy/show for collaborator/owner/host tokens, developer diagnostics bundle JSON) into react-app/domains/settings/pages/config-view.tsx. Source file was already prop-driven so the port is a 1:1 JSX and effects translation.

Made-with: Cursor

* feat(react-app/settings): port ExtensionsView to React

Ports the extensions tab shell (all/mcp/plugins filter pills, counters, mcp/plugins section composition) into react-app/domains/settings/pages/extensions-view.tsx. Inverts the Solid useConnections() dependency: the caller passes mcpConnectedAppsCount and the already-rendered McpView as props, so this page can ship before the connections provider is ported.

Made-with: Cursor

* docs(react-app): add ARCHITECTURE.md describing the domain-based tree

Captures the top-level layout (shell/kernel/infra/design-system/domains),
the domain breakdown (session/workspace/settings/connections/bundles/shell-feedback),
the provider composition flow from index.react.tsx down, where state lives,
the framework-agnostic boundary with app/, the porting pattern (move-and-re-export
+ context-to-props), and the temporary shim layer under src/react/.

Made-with: Cursor

* feat(react-app/session): port draft store to React

* feat(react-app/session): port run state helpers

* feat(react-app/session): port session actions store

* feat(react-app/session): port actions provider

* feat(react-app/session): port transcript scroll controller

* feat(react-app/session): port grouped transcript rendering

* feat(react-app/session): port composer tool affordances

* feat(react-app/session): port workspace session sidebar

* feat(react-app/workspace): port share workspace state

* feat(react-app/settings): port authorized folders panel

* feat(react-app/settings): port settings page chrome

* feat(react-app/settings): port settings shell layout

* feat(react-app/settings): port general settings view

* feat(react-app/settings): port appearance settings view

* feat(react-app/settings): port updates settings view

* feat(react-app/settings): port recovery settings view

* feat(react-app/settings): port den settings panel

* feat(react-app/settings): port den settings view

* feat(react-app/settings): port messaging settings view

* feat(react-app/settings): port MCP settings view

* feat(react-app/connections): port openwork server store

* feat(react-app/connections): port openwork server provider

* feat(react-app/settings): port skills settings view

* feat(react-app/settings): port automations settings view

* feat(react-app/settings): port advanced settings view

* feat(react-app/settings): port debug settings view

* feat(react-app/connections): port connections store

* feat(react-app/connections): port provider auth modal

* feat(react-app/connections): port MCP auth modal

* feat(react-app/connections): port connections modals

* feat(react-app/shell): port font zoom behavior

* feat(react-app/shell): port workspace shell layout

* feat(react-app/session): port session page

* feat(react-app/shell): wire session route in app root

* feat(react-app/shell): add top-level app routes

* feat(react-app/shell): switch app entry to React

* feat(react-app/settings): port extensions store

* feat(react-app/settings): port automations store

* chore(react-app): finalize React-only app cutover

* fix(react-app/shell): hydrate OpenWork settings from env

* fix(react-app/shell): restore full-height app shell

* fix(react-app/routes): restore workspace bootstrap flows

* fix(react-app): streaming, rename/delete session, model picker

* fix(react-app/settings): break infinite loop in model picker + local setUi effects

* docs(evals): capture 9 React session/settings flows for LLM replay

* feat(react-app): restore command palette, workspace options menu, desktop boot, missing i18n

- Add Cmd+K command palette (root + Sessions sub-mode), restoring a flow that was deleted with the Solid tree and never re-ported.
- Add Cmd+N shortcut to create a new session directly (distinct from Cmd+K).
- Wire workspace options menu: Edit name, Reveal in Finder, Share (path to clipboard), Remove workspace.
- Add RenameWorkspaceModal, workspaceUpdateDisplayName, workspaceForget bindings.
- Always-show +/... buttons on the selected workspace row (prev hover-only, broke in Tauri).
- Desktop dev bootstrap hook: starts openwork-server + engineStart + orchestratorWorkspaceActivate on Tauri startup and emits openwork-server-settings-changed so routes re-resolve.
- Restore 17 missing i18n keys leaking as raw identifiers (session.default_model, session.select_or_create_session, settings.default_label, workspace.create_workspace, etc).
- Extend evals/react-session-flows.md with flows 10-12 + desktop runner notes.

* new stuff

* feat(react-app): composer parity with solid shell

- Composer layout matches solid: sticky gradient wrap, 24px panel, menus render
  above the panel, attach/tools/send inline with editor, agent+model+behavior
  strip below the panel.
- Tools menu now surfaces commands + skills + MCPs with MCP status badges and a
  "Configure" settings affordance.
- Attachments gain the 8MB cap, mime allowlist, image compression, better cards,
  and localized notices (file_exceeds_limit / unsupported_attachment_type).
- Pasted-text chips expose expand/copy/remove actions; clipboard links now
  normalize file://, Windows and UNC paths and surface the unsupported-file
  notice.
- Composer now listens for openwork:focusPrompt + openwork:flushPromptDraft and
  blocks Enter during IME composition in both the wrapper and the lexical
  submit command.
- Inbox upload button and handler wired to openworkClient.uploadInbox with
  success/error notices.
- SessionRoute now opens ModelPickerModal in place (instead of navigating to
  /settings/general) and lazily loads providers when the picker opens.
- SettingsRoute mounts ConnectionsModals so the MCP OAuth flow actually shows
  its auth modal after addMcp.
- Boot flow: fast-path when engine is already running; orphan event
  subscriptions disposed immediately so repeated workspace switches stop
  leaking event streams. Sidebar collapses non-selected workspaces to keep
  rapid switching responsive.
- BootStateProvider + LoadingOverlay + session-memory land under react-app
  shell for a consistent startup experience.

* fix(react-app/session): center transcript in 800px column

Wrap SessionTranscript in mx-auto max-w-[800px] so user and assistant
messages share the same centered column as the composer, matching the
reference chat layout.

* fix(react-app/session): let the user break out of autoscroll during streaming

Previously a programmatic scroll-to-bottom fired on every content-height
change during streaming, and the onScroll handler early-returned while
that flag was set. In practice the ResizeObserver re-anchored the user
to the tail of the transcript faster than their wheel gesture window
could win, so scrolling up felt hijacked.

Now:
- onScroll detects a user gesture (wheel/touch/trackpad/scrollbar) or
  a meaningful upward delta and aborts the in-flight programmatic
  state, switching to manual-browse immediately.
- The ResizeObserver no longer auto-scrolls while the user is actively
  gesturing, even if we're still technically in follow-latest, so the
  transcript stops fighting the user during streaming.
- Widened the gesture window from 250ms to 600ms and added a 16px
  upward threshold to filter anchoring jitter from real intent.

* fix(react-app/session): make workspace rename + remove actually work

Rename and remove were being applied only to the desktop side (Tauri
workspaces.json) while the openwork-server's /workspaces list kept the
old name and a still-present row, and then the next refreshRouteState
overwrote the desktop list with the server one because we prefer the
server-returned workspaces for correct IDs. As a result, clicking Edit
name or Remove workspace in the sidebar menu did nothing visible.

- handleSaveRenameWorkspace now also calls client.updateWorkspaceDisplayName
  so the server reflects the new name on the next refresh.
- handleForgetWorkspace now also calls client.deleteWorkspace, confirms
  with the user first, clears the sidebar selection + last-session
  memory if the removed workspace was active, and navigates back to
  /session so the main pane stops referencing a gone workspace.
- refreshRouteState overlays desktop displayName onto the matching
  server workspace (by id or normalized path) and filters out any
  server row that the desktop no longer knows about, so rename / remove
  take effect instantly instead of flickering back.
- Added workspace_list.remove_confirm to the en locale.

* fix(react-app/session): stop draft leak + transient workspace/session mismatch

- SessionSurface now clears the composer draft when sessionId changes. The
  existing reset effect was clearing attachments/mentions/pasteParts but
  not draft itself, so typed text bled across every session switch.
- SessionRoute refuses to render SessionSurface when the current
  selectedSessionId doesn't belong to the new selectedWorkspaceId. That
  transient mismatch happens for one tick after clicking a different
  workspace, between setSelectedWorkspaceId and the router landing on the
  remembered session id. SessionPage.canRenderReactSurface now also
  requires a non-null surface prop so spreading null is impossible.
- Added a runtime inspection surface (window.__openwork) used by the
  session route and SessionSurface. The route and composer each publish a
  live slice (workspaces, selected ids, draft state, attachments, mentions,
  pasteParts, busy/error state) and append events on refresh/mount.
  External drivers (devtools, chrome-mcp) can call .snapshot() / .slice()
  / .events() to inspect the app without walking the DOM.

* fix: opencode proxy content-encoding + new task flow

- openwork-server's proxyOpencodeRequest/proxyOpenCodeRouterRequest now
  returns a sanitized Response that strips content-encoding /
  content-length / transfer-encoding. Bun's native fetch already decodes
  the upstream body but keeps the upstream headers, so the browser would
  see Content-Encoding: gzip on a plain JSON payload and fail with
  ERR_CONTENT_DECODING_FAILED. This broke session.create (and anything
  else reaching through /w/<id>/opencode/*) from Chrome/web clients.
- SessionRoute.surfaceProps now only refuses to render when the URL
  session is known to belong to a *different* workspace. A brand-new
  session that hasn't appeared in any workspace's list yet still
  renders, so 'New task' feels instant.
- onCreateTaskInWorkspace optimistically inserts the freshly-created
  session into sessionsByWorkspaceId, writes it as the remembered
  session for the workspace, navigates, and then background-refreshes
  the route so the server-assigned title/timestamp catches up.

Remember to run `pnpm --filter openwork-server build:bin` after any
apps/server/src change — the desktop app spawns the built binary, not
the TS source.

* feat(observability): dev log sink + react console/error/hang forwarder

- openwork-server gets a POST /dev/log endpoint that appends JSON lines to
  the path in OPENWORK_DEV_LOG_FILE. Unauth on purpose because it runs
  only when the env var is set on the dev host.
- React app ships a debug-logger that:
  - patches console.log/info/warn/error/debug to forward to /dev/log
  - captures window.onerror + unhandledrejection
  - wraps fetch to record every request's URL, method, status, duration
    and a list of in-flight requests the inspector can read
  - runs a 1s heartbeat that logs a 'hang' entry when the main thread
    stalls more than 3s, including the in-flight fetch sample
  - publishes a 'debug' slice on window.__openwork with pendingFetches +
    memory
  - uses a captured native fetch for its own flush so it doesn't recurse
  - skips recording /dev/log traffic so it can't log itself
- scripts/openwork-debug.sh: snapshot the dev stack, tail all logs
  (pnpm dev + /dev/log sink), probe server/opencode/router health, and
  clean orphan processes (parent == launchd).

* fix(react-app): auto-recover from webview background throttling

The biggest driver of 'the app becomes inactive after a while' on macOS
is WKWebView's aggressive background throttling: setIntervals pause and
in-flight fetches can sit idle for minutes, so when the user refocuses
the app, cached state is stale and the refresh guard (refreshInFlightRef)
is still stuck true from the interrupted request.

- debug-logger now distinguishes between a real hang (3-10s main-thread
  stall) and a post-throttle resume (>10s gap). Only the former gets a
  'hang' entry; the latter is logged as a meta event with a clear
  'Webview resumed after Xs' message so operators don't chase a
  non-bug.
- debug-logger listens for visibilitychange and, on return to visible,
  dispatches 'openwork-server-settings-changed' to kick the app into
  re-resolving its server connection and re-fetching route data.
- session-route self-heals refreshInFlightRef on both the settings
  change event AND on visibility flip so a refresh that was stuck
  mid-flight during throttling doesn't block every subsequent refresh.

Together this turns the long silent 'frozen' state after backgrounding
into an automatic recover-and-refresh cycle.

* feat(composer): plain Enter sends; remove 'upload to shared folder' button

- Enter submits the composer by default; Shift+Enter inserts a newline.
  Cmd/Ctrl+Enter still works for muscle memory. IME composition guard
  preserved so CJK input never triggers a submit mid-character.
- Removed the Upload (shared folder / inbox) button, its hidden file
  input, the helper, and the unused lucide Upload import. The
  onUploadInboxFiles prop stays available for the remote-paste notice
  flow but no longer has a dedicated button in the action row.

* fix(app/http): bypass Tauri HTTP plugin for streaming endpoints

The dev log sink captured repeated 10-minute main-thread stalls on the
desktop webview, every one with pendingFetchCount=1 and the stuck
request being ipc://localhost/plugin%3Ahttp%7Cfetch_read_body. Tauri's
HTTP plugin only resolves the response body when the whole body has
been delivered; pointed at an SSE endpoint (opencode /event or
openwork-server /workspace/<id>/events) the body never closes so the
IPC call hangs forever and the webview's queue backs up until the UI
looks completely frozen.

- apps/app/src/app/lib/opencode.ts: createTauriFetch now detects
  streaming requests (URL contains /event or /stream, or the caller
  set Accept: text/event-stream) and routes them through the webview's
  native window.fetch instead of tauriFetch. Those requests also opt
  out of the 10s transport timeout so long-lived SSE subscriptions
  aren't aborted — callers abort via AbortSignal when they're done.
- apps/app/src/app/lib/openwork-server.ts: resolveFetch(url) now
  switches to native fetch for URLs that match the SSE pattern
  (/events or /event-stream or /stream) and keeps tauriFetch for every
  other desktop request so CORS-sensitive endpoints still work.
- CORS is already wide open on openwork-server (--cors *) so there's
  no regression from using native fetch for streams from the Tauri
  webview.

* fix(react-app): publish engine baseUrl to ServerProvider on desktop boot

Move DesktopRuntimeBoot inside ServerProvider so the boot hook can call
useServer().setActive with the real engine baseUrl from both the fast path
(engineInfo probe) and slow path (engineStart). Fixes chat on Windows where
the engine binds to a dynamic port (e.g. 64357) instead of the default 4096
that ServerProvider was stuck on.

* fix(react-app): restore thinking-variant picker + Cmd+K top icons; tighten chat column

Three unintentional regressions from the React port:

1. Thinking / reasoning variant picker was empty because session-route
   never threaded modelBehaviorOptions into SessionSurface. Now we
   prefetch opencode's provider catalog as soon as the workspace
   opencode client is available, and compute
   getModelBehaviorSummary(providerID, model, variant) for the current
   default model to produce both modelVariantLabel and
   modelBehaviorOptions. The composer's Default/Thinking/Minimal etc.
   pill works again.

2. Cmd+K command palette lost the top-bar shortcuts. Added entries for
   Open documentation, Send feedback, and direct jumps to every
   settings tab (Skills, Extensions, Messaging, Appearance, Recovery,
   Updates) alongside the existing New session / Sessions / Settings
   rows. onOpenSettings now accepts an optional route so the palette
   can drop the user straight into the matching tab.

3. Chat transcript column tightened from 800px to 720px so messages
   don't sit at the same width as the composer and feel too big. The
   composer stays at 800px for action-bar breathing room.

* fix(composer): compact editor + show real default variant instead of 'Provider default'

- Composer used way more vertical space than intended. The editor had
  min-h-[180px] on the wrapper + min-h-[140px] on the ContentEditable
  so the starting height was ~180px. The panel padding (p-5 md:p-6)
  added another ~40px. Reset to a tight single-line look
  (min-h-[24px]) and cap the ContentEditable at max-h-[220px] with
  overflow-y-auto so long pastes scroll inside the composer instead
  of pushing the transcript off screen.
- Drop the generic 'Provider default' row from the reasoning/thinking
  variant menu. The list now shows only concrete variants
  (Low / Balanced / Deep / …). When no override is saved we resolve
  the provider's actual default preset (OpenAI/Google reasoning →
  medium, Anthropic extended-thinking → none, etc.) and use its
  label for the pill, so the pill is honest about what will run.
  Variant menu highlights the row whose label matches the pill, so
  'Balanced' appears selected when the provider default is medium.
- Editor placeholder label sizing normalized to 15px/24px line height
  to match the new compact composer geometry.

* perf(session): reduce large-transcript render cost

The current freeze investigation pointed at frontend render cost more than
fresh exceptions: a giant session loaded with a huge pasted-email block,
and Chrome DevTools itself timed out when we tried to interact with it.
The transcript was only virtualized after 500 message blocks, which means
sessions with a few enormous messages still rendered a giant DOM eagerly.

This patch makes the transcript cheaper:
- lower virtualization threshold from 500 -> 40 blocks so react-virtual
  kicks in much earlier for realistic long sessions
- apply content-visibility:auto sooner (24 blocks) and reduce the
  intrinsic placeholder size so distant blocks stay out of layout/paint
- add contain: layout style paint to block wrappers to isolate large
  message subtree layout work
- avoid running the text-highlight walk when search is inactive; if no
  query is set, only clear highlights when marks actually exist instead
  of traversing every large message DOM tree on every render

After this change the previously-problematic giant session
(ses_262bc60dfffefubKIJNQ23S3h2) still loads and, importantly, accepts a
new prompt + assistant reply in Chrome without wedging.

* feat(openwork-debug): proper layered teardown + reset subcommand

When a pulled PR doesn't take effect on the running desktop app, the
cause is almost always a wedged Vite dev server still attached to the
previous module graph, plus the Tauri webview holding its own HMR
client cache, plus the dep pre-bundle cache in node_modules/.vite. The
fix is multi-step and easy to get wrong by hand, so codify it.

scripts/openwork-debug.sh now supports:
  start          nohup pnpm dev with the /dev/log sink on
  stop           layered teardown (pnpm dev -> tauri dev -> webview ->
                 Vite -> openwork-server/orchestrator/opencode/router ->
                 orphan sweep)
  reset          stop + wipe Vite caches (apps/app/node_modules/.vite,
                 root + apps/desktop too) + truncate sink + start +
                 wait-healthy
  restart        alias for reset
  wait-healthy   block until openwork-server /health returns 200
  status         alias for snapshot
  reset-webview  destructive: wipe the dev WKWebView WebsiteData when a
                 plain reset still leaves the UI stuck on a stale URL
                 override

Safety:
  - Tauri webview is matched by full 'target/debug/OpenWork-Dev' path so
    the installed /Applications/OpenWork.app is never killed by mistake.
  - pnpm dev teardown uses the PID file written at start time, falling
    back to a path-aware pkill so we don't nuke pnpm runs in other
    repos on the same host.
  - reset preserves ~/Library/Application Support/com.differentai.openwork.dev
    (tokens, workspaces registry, prefs) and only clears Vite ephemera
    plus the log sink file (truncated).

This replaces the previous ad-hoc 'kill pnpm; kill vite; rm -rf .vite'
dance users had to do when HMR went stale after pulling changes.

* fix(composer): paste preserves newlines, no path hijack, no emojis

Two longstanding composer bugs:

1) Paste was fucked. The onPaste handler hijacked any plain-text paste
   whose lines looked 'path-ish' (anything starting with '/', file://,
   UNC, Windows drive letter) via parseClipboardLinks + preventDefault,
   and it also force-replaced any paste >10 lines with a
   'pasted text' chip. Together this meant normal email/code/legal
   pastes either vanished or collapsed into a pill.

   New policy:
     - clipboard files  -> attach (unchanged)
     - clipboard text/uri-list  -> treat as external URL drop, insert
       links via onUnsupportedFileLinks (drag-from-Finder path only)
     - everything else (plain text)  -> DO NOTHING, let Lexical handle
       the paste natively so newlines render and no content is lost
     - remote/sandbox workspace warning is now advisory only; the paste
       still goes through
   Removed the now-dead countLines helper and the WINDOWS_PATH_RE /
   UNC_PATH_RE constants. parseClipboardLinks becomes the
   uri-list-only parseClipboardUriList.

2) Emojis in the composer. Mention pills rendered '🤖 ' / '📄 ' and
   the pasted-text chip rendered '📋 N lines'. Removed all three. The
   pill background/foreground already communicates the kind. The paste
   chip now reads 'Pasted · N line(s)'.

* fix(composer): only render pasted-text chip inline, drop duplicate rail

The pasted-text chip was rendered twice: once inline inside the Lexical
editor via ComposerPastedTextNode, and again as a separate rail above
the composer. Deleted the rail so there's a single, inline
representation. The inline chip keeps its label + line-count pill and
users remove it with backspace like any other inline token.

* fix(session): allow appending prompts mid-stream; point feedback button at feedback

Two regressions from the React port:

1) Couldn't append a new prompt while the assistant was still producing
   a response. SessionSurface.handleSend short-circuited on
   chatStreaming, and the composer swapped the Send button out for a
   Stop button whenever busy. Together this made mid-stream follow-ups
   impossible.

   - handleSend now only short-circuits when the draft is empty. Sending
     while streaming is allowed; OpenCode accepts follow-up user turns
     mid-run, and any error surfaces via setError.
   - Composer action row now keeps Send reachable during streaming:
     when busy + draft has text, Stop and Send render side-by-side.
     When busy + draft empty, only Stop shows.

2) The bottom-right 'Send feedback' icon in the status bar used to
   openLink('https://openworklabs.com/docs'), so clicking feedback
   opened the docs. SessionRoute now uses buildFeedbackUrl({
   entrypoint: 'status-bar', appVersion: '0.11.207' }) from
   apps/app/src/app/lib/feedback.ts — the same helper the Solid app
   used — so the button opens the real feedback form with source +
   client OS context populated.

* feat(dev-profiler): React Profiler overlay for pnpm dev

Dev-only overlay that shows which React zones re-render and how long
they take, so operators can immediately see which subtree is thrashing
when the UI feels stuck.

Mechanics:
- apps/app/src/react-app/shell/dev-profiler.tsx: exports DevProfiler
  (wraps a subtree with React's <Profiler>) and DevProfilerOverlay
  (small floating card, bottom-right).
- Aggregates every commit into a Map keyed by zone id: count, total
  actual ms, base ms, last commit ts, mount vs update counts. Emits
  to subscribers via rAF-throttled dispatch so a stream of commits
  can't flood setState.
- Overlay is opt-in. Toggle with Cmd/Ctrl+Shift+P; last state is
  persisted in localStorage.openwork.debug.profilerOverlay. Off by
  default.
- In prod builds the wrapper is a pure pass-through (no <Profiler>
  mounted, no overhead) and the overlay renders null. Gate via
  import.meta.env.DEV.
- Exposes the same snapshot at window.__openwork.slice('profiler') so
  Chrome MCP / external drivers can read render counts without the
  overlay being visible.

Instrumented zones so the table fills up with real work:
- AppRoot, SessionRoute, SettingsRoute (top-level routing)
- SessionSurface (chat pane)
- SessionTranscript (message list)
- SessionComposer (composer tree)

Using it:
1. pnpm dev
2. open the dev app
3. press Cmd+Shift+P
4. watch the table as you interact — hottest zones bubble to the top,
   rows flash when they commit so you can visually attribute bursts.
5. hit 'reset' in the overlay header to clear counters between tests.

* fix(model-behavior): use vendor-canonical thinking-mode nomenclature

The composer's variant labels were generic ('Light / Balanced / Deep /
Maximum'), which didn't match the terminology users already know from
the vendors' own products. Switched to vendor-aligned labels per
provider family so the composer pill shows names matching ChatGPT /
Claude / Gemini / Grok conventions.

Mapping (per provider family, by OpenCode variant key):

  OpenAI / ChatGPT / Azure / OpenCode
    none or minimal -> Instant
    low             -> Light thinking
    medium          -> Thinking
    high            -> Thinking longer
    xhigh / max     -> Maximum thinking
    section title   -> 'Reasoning effort'

  Anthropic / Claude
    none            -> No extended thinking
    low             -> Brief extended thinking
    medium          -> Extended thinking
    high            -> Deep extended thinking
    xhigh / max     -> Maximum extended thinking
    section title   -> 'Extended thinking'

  Google / Gemini
    none            -> Instant
    low             -> Brief thinking
    medium          -> Thinking
    high            -> Deep thinking
    xhigh / max     -> Maximum thinking
    section title   -> 'Thinking budget'

  xAI / Grok
    none            -> Fast
    low / medium    -> Think
    high            -> Think harder
    xhigh / max     -> Think hardest
    section title   -> 'Think mode'

Each row's description was also rewritten per family to match. The
previous generic labels remain as the fallback for providers outside
these four families.

Also added resolveProviderFamily(providerID) to centralize the family
bucketing so getVariantLabel / getVariantDescription / getBehaviorTitle
stay consistent.

* fix(dev-profiler): stop the profiler from profiling itself into a loop

The React Profiler overlay produced absurd numbers (1.1M+ commits per
zone, ~22min of render work) because it re-rendered itself inside the
AppRoot Profiler zone every time it recorded a commit — which itself
scheduled another commit, forever.

Why:
  commit somewhere -> recordCommit -> scheduleEmit (rAF)
  emit -> every subscriber's setSnapshot(...)
  -> overlay re-renders (inside AppRoot's <Profiler>)
  -> another commit -> recordCommit -> emit -> ...

Two changes:

1. DevProfilerOverlay now splits into two components.
   - DevProfilerOverlayToggle owns visibility + the Cmd+Shift+P bind
     and does NOT subscribe.
   - DevProfilerOverlayVisible is only mounted when the overlay is
     visible and is the sole subscriber to profiler snapshots.
   When the overlay is hidden (the default), nothing is subscribed.

2. recordCommit / scheduleEmit short-circuit when subscribers.size === 0.
   Profiler zones are therefore zero-cost when the overlay is off: no
   Map writes, no rAF scheduled, no emit, no re-render chain.

When the overlay is on you still get the intended behavior (rows
update per commit, flashing, reset button, totals), but the overlay's
own re-renders no longer propagate endlessly through the tree.

Prod builds: untouched (the wrapper is already a pass-through and the
overlay returns null when !import.meta.env.DEV).

* perf(session): memoize SessionTranscript; lift profiler overlay out of AppRoot zone

The previous profile (1144 AppRoot commits, 55 SessionTranscript at
27ms each) made two things obvious:

1) AppRoot's commit count was inflated by the dev profiler overlay's
   own re-renders. The overlay subscribes to commits to refresh its
   table, and it was mounted inside <DevProfiler id='AppRoot'>, so
   every overlay self-render registered as an AppRoot commit (~1073 of
   the 1144). Now the overlay sits OUTSIDE the AppRoot Profiler zone
   so AppRoot's count reflects real app-level commits only and its
   children's relative numbers stay readable.

2) SessionTranscript was committing 55 times for 59 SessionSurface
   commits at ~26 ms per commit (right at the 16ms frame budget). The
   counts being almost identical means the transcript was re-rendering
   on every parent commit instead of only when its own props changed.
   Wrapping with React.memo at the transcript boundary lets it skip
   re-renders when SessionSurface ticks for unrelated state (sending,
   chatStreaming, attachment changes, etc.). The transcript component
   itself remains the same; the public export is a memoized wrapper
   over SessionTranscriptInner with displayName preserved.

Composer was already cheap (~0.5ms/commit), so left as-is. Next pass
should reduce SessionSurface state churn during streaming so the
parent commits 5-10x less often.

* fix(dev-profiler): opt-in only; disable by default in dev

Symptom: open an old session, type 'tell me a long story', press
Enter. A few tokens stream in, then the webview wedges. Log contains:
  Uncaught DataCloneError: Failed to execute 'measure' on
  'Performance': Data cannot be cloned, out of memory.
  at logComponentRender (react-dom_client.js)

Root cause: the React <Profiler> zones I added (AppRoot, SessionRoute,
SessionSurface, SessionTranscript, SessionComposer) cause React-DOM's
internal instrumentation (logComponentRender) to emit
performance.measure entries on every commit. Sustained streaming
commits fill the browser's performance timeline faster than it can be
reclaimed; once the allocation cloner runs out of space the commit
throws mid-stream and the main thread stalls for everyone.

Fix: make the profiler fully opt-in. Default is OFF even in dev. Two
opt-ins:
  - VITE_OPENWORK_PROFILER=1 at pnpm dev
  - window.localStorage.setItem('openwork.debug.profiler', '1')

When off, <DevProfiler> is a pure pass-through (no <Profiler> mounted,
no overhead) and <DevProfilerOverlay> renders null. The Cmd+Shift+P
overlay toggle is unchanged for opted-in dev runs.

* perf(session): correct virtualizer + memoize markdown blocks

Audit of why large sessions still hung 'after 2 words' during streaming
turned up four real virtualizer bugs and one markdown hot spot:

1. shouldVirtualize required props.scrollElement() to already be
   non-null. On the first render of a large session the scroll
   container ref hadn't attached yet, so the whole transcript rendered
   eagerly (every message block) for one tick before switching to
   virtualization. That burst alone froze the UI on huge transcripts.
   Now shouldVirtualize only depends on block count.

2. A useEffect called virtualizer.measure() on every messageBlocks
   change. During streaming messageBlocks gets a new identity on every
   token, so we forced a synchronous re-measure (every row's
   getBoundingClientRect) on every token. react-virtual already
   invalidates rows whose refs or content change. Deleted the effect.

3. The render had a second eager fallback: virtualRows.length > 0
   ? <virtualized> : <every message eagerly>. That re-introduced the
   same freeze on the very first virtualized render before rows were
   computed. Removed; if shouldVirtualize is true we always render the
   virtualized container, even when getVirtualItems() is empty, so the
   tree stays bounded.

4. estimateSize was a flat 220 for every block. Giant messages and
   tiny user-user bubbles share the same bad guess, so react-virtual
   constantly fought the real sizes. Replaced with a shape-aware
   estimate (steps-cluster: 80, user: 96, assistant: 320).

5. VIRTUALIZATION_THRESHOLD lowered from 40 -> 20. Medium sessions are
   cheaper to render via the virtualizer than as a flat list of 20+
   markdown-heavy blocks.

6. MarkdownBlock was not memoized. Every streamed token re-parsed
   markdown for every visible message. Wrapped it in React.memo so
   blocks other than the currently-streaming one skip re-render when
   their own text prop didn't change.

Also fixed an unrelated TS error in session-sync (missing
deltaFlushBuffer / deltaFlushScheduled fields on the SyncEntry factory
literal) that was blocking typecheck.

* perf(session): structural sharing for messageBlocks during streaming

Port the useStableRows idea from T3Tools' MessagesTimeline. On every
streaming token, props.messages is a fresh array but only the
currently-streaming message has a new UIMessage reference — every
other message in the transcript is still pointer-equal to last tick
(session-sync.ts uses messages.slice() + targeted mutation).

Our messageBlocks useMemo previously reconstructed every block object
from scratch on every token, so downstream React.memo'd components
(MarkdownBlock especially) always saw fresh prop references and
couldn't bail out. Structural sharing fixes that:

- blockIdentityKey(block): derives a stable key per block ('msg:<id>'
  for message blocks, 'cluster:<id>' for step clusters).
- blocksAreEquivalent(prev, next): returns true when the two blocks
  point at content-equal data. For message blocks the critical check
  is prev.message === next.message (UIMessage reference). For step
  clusters we compare messageIds + stepGroups identity.
- SessionTranscriptInner keeps a previousBlocksRef<Map<key, block>>.
  After the raw messageBlocks array is computed it maps each entry
  through useStableBlocks-equivalent logic: if the previous block at
  the same key is content-equivalent we reuse the previous reference.

Net effect: during a streaming burst, only the active assistant
message's block gets a new identity per token. All other rows hand
pointer-equal props to their memoized children, which bail out of
rendering entirely. This is the highest-leverage fix available for
the 'big sessions freeze after a few streamed words' symptom.

* fix(session-sync): infer stub role when parts arrive before message.updated

When message.part.updated or message.part.delta arrives for a
messageID we haven't seen a message.updated for yet, we stub the
message so the part has somewhere to live. The stub's role was
hard-coded to 'assistant', which lost the race during a promptAsync
flow: if the server emitted a part event for a user turn before the
user message.updated, the new user message flashed as an assistant-
styled block (left-aligned, markdown, no bubble) until the real role
arrived a tick later. User saw 'sometimes my message renders outside
the user bubble, as if it were assistant'.

Fix: infer the stub role from the conversation. Chat turns alternate
so a new unknown message is almost always the opposite role of the
most recent known message. If the transcript is empty the first
message is always the user's.

- New inferStubRole(messages): returns 'user' when the last message
  was 'assistant' (and vice versa), defaulting to 'user' when there
  is no prior message.
- message.part.updated / delta-flush stubs now call inferStubRole if
  the message isn't already in state. If it is, they preserve the
  existing role to avoid flapping.

upsertMessage already re-applies the authoritative role as soon as
the real message.updated event arrives, so this is purely a correct-
on-first-paint improvement and doesn't affect long-term state.

* fix(dev-logger): stop spamming 404s when the /dev/log sink is disabled

The React debug-logger posts console/error/fetch/hang events to the
openwork-server /dev/log sink. When pnpm dev is started without
OPENWORK_DEV_LOG_FILE set, the server returned 404 from both GET and
POST /dev/log. The browser logs every failed POST as
'Failed to load resource: 404' regardless of whether the logger
silently handles the error, so an operator who doesn't know about the
env var sees console noise on every page.

Two changes:

1) Server probe now returns 200 + {ok:false, reason:'dev_log_disabled'}
   instead of 404. Clients that want to know whether the sink is active
   should read the body flag, not the status. This eliminates the
   console warning from the probe itself.

2) Client now probes /dev/log once per base URL with GET. If the
   response body says the sink is disabled, it caches that decision
   and skips all further POSTs for the remainder of the session. In-
   memory events remain available via window.__openwork.events().

Net effect: sink-disabled dev sessions produce zero /dev/log traffic
and zero console 404s. Sink-enabled sessions behave as before.

* feat(app): wire share-workspace modal; add alpha release channel concept

Share workspace (React port):
- session-route wires the full ShareWorkspaceModal + useShareWorkspaceState,
  with remote-access toggle (local workspaces), workspace-profile publish,
  skills-set publish, team/public template flows, and workspace export.
- Route now tracks OpenworkServerInfo, EngineInfo, and settings version so
  the share state has enough context to resolve the mounted local workspace
  URL and host tokens.

Alpha release channel:
- New ReleaseChannel type ("stable" | "alpha").
- apps/app/src/app/lib/release-channels.ts exposes STABLE/ALPHA updater
  endpoints, resolveUpdaterEndpoint, isAlphaChannelSupported, and a
  coerceReleaseChannel helper.
- LocalPreferences carries releaseChannel (default "stable").
- Updates settings shows a macOS-only Stable/Alpha toggle.
- .github/workflows/alpha-macos-aarch64.yml now publishes signed+notarized
  macOS arm64 builds to a rolling alpha-macos-latest GitHub release with
  a Tauri updater latest.json on every merge to dev.
- ARCHITECTURE.md documents the channel contract.

* feat(react-app/cloud): port Den auth + desktop-config + forced-signin gate

Ports the cloud-domain pieces dev added on Solid so the React shell has
feature parity for:

- DenAuthProvider / useDenAuth — drives auth status + user, calls
  ensureDenActiveOrganization() on every refresh so Better-Auth's
  active org stays in sync (Solid: apps/app/src/app/cloud/den-auth-provider.tsx)

- DesktopConfigProvider / useDesktopConfig / useOrgRestrictions — fetches
  the org-scoped DesktopAppRestrictions (blockZenModel, disallowNonCloudModels,
  etc.) introduced in packages/types, caches it per base-url+org in
  localStorage so the gate resolves synchronously on next boot, and
  re-fetches on denSessionUpdatedEvent / denSettingsChangedEvent and a
  1h interval (Solid: apps/app/src/app/cloud/desktop-config-provider.tsx)

- DenSignInSurface — shared presentation surface for both the forced
  sign-in page and any embedded 'panel' usage; matches Solid props 1:1
  so flows are interchangeable
  (Solid: apps/app/src/app/cloud/den-signin-surface.tsx)

- ForcedSigninPage — full-screen sign-in gate that owns local drafts
  (base URL, manual auth input) and pipes them into DenSignInSurface.
  Handles manual auth input parsing (openwork:// deep links and raw
  grant codes) and base-URL overrides via setDenBootstrapConfig
  (Solid: apps/app/src/app/cloud/forced-signin-page.tsx)

Shell wiring:

- providers.tsx mounts DenAuthProvider > DesktopConfigProvider inside
  GlobalSyncProvider so all routes have access to auth + org restrictions
- app-root.tsx adds a /signin route and a DenSigninGate component that
  enforces readDenBootstrapConfig().requireSignin: redirects unsigned
  users to /signin and signed-in users away from /signin. Renders null
  while the first Den session check is still in flight so no
  session/settings chrome flashes behind the gate.

Better-Auth active-org:

- den-settings-panel.tsx now calls ensureDenActiveOrganization({
  forceServerSync: true }) both in refreshOrgs and in the org <select>
  change handler so subsequent /v1/org/* requests resolve against the
  freshly selected org (Solid ac41d58b).

Shared lib:

- den.ts re-exports the DenDesktopConfig type alias so React consumers
  can import it from the same module as its helpers. Solid referenced it
  only internally.

* feat(react-app/cloud): port dev #1476 + #1505 + #1509 + #1512

Completes the cloud-domain feature parity audit: everything dev shipped
on Solid that hadn't yet been reachable from the React shell now has a
React-native equivalent.

Desktop restriction enforcement (#1505):
- desktop-config-provider exposes a stable checkRestriction closure
  matching DesktopAppRestrictionChecker from cloud/desktop-app-restrictions
- New hooks: useCheckDesktopRestriction() and useDesktopRestriction(key)
- RestrictionNoticeProvider owns a single app-wide RestrictionNoticeModal
  and exposes { show, dismiss } via useRestrictionNotice()
- session-route now gates "Create workspace" on blockMultipleWorkspaces
  and surfaces the admin notice when blocked
- session-route filters the model picker against blockZenModel and
  disallowNonCloudModels before the search filter runs, so blocked
  providers/models are never offered
- settings-route filters the provider-auth modal's provider list through
  the same desktop-provider gate so users can't connect a forbidden one

Cloud provider auto-sync (#1509):
- Ported cloud/sync/constants.ts already came in via the merge
- New useCloudProviderAutoSync(refresh) hook runs refreshCloudOrgProviders
  on mount and every CLOUD_SYNC_INTERVAL_MS while Den auth is signed-in;
  skips while signed-out and avoids overlapping ticks via an in-flight ref
- Mounted from settings-route (which already owns the provider-auth store)

Cloud-id drift protection (#1510):
- import-state.ts + provider-auth/store.ts track sourceProviderId so we
  can detect when the provider's server-side id drifts from what we
  imported (enables rename-aware de-dup later)
- ProviderIcon gained the providerName prop so cloud-id-keyed providers
  still resolve the right icon via the provider-family detector

Desktop update gating (#1476 + #1512):
- New lib/version-gate.ts exposes compareVersions, parseComparableVersion,
  isUpdateAllowedByDesktopConfig (allowedDesktopVersions), and
  isUpdateSupportedByDen (Den /v1/app-version round-trip)
- Combined isUpdateAllowed() helper is the single entry point the React
  updater flow should call once it's wired (pre-existing TODO)

Not yet wired (explicit follow-ups, none of them user-facing gaps today):
- React checkForUpdates itself is still stubbed; version-gate helpers are
  ready to plug in when that land
- Full workspace-config-side restriction reconciliation
  (runDesktopAppRestrictionSyncEffects) — our React provider store
  doesn't yet expose ensureProjectProviderDisabledState; when it does,
  wire the sync-effects call from app-root.

* fix(react-app): stop idle memory growth in the shell

---------

Co-authored-by: Jan Carbonell <jc2897@cornell.edu>
2026-04-21 22:25:28 -07:00
Source Open
daff81bef4 feat(app): gate desktop updates by org config (#1512)
* feat(app): gate desktop updates by org config

Return allowed desktop versions through the signed-in desktop config endpoint and only surface Tauri updates when both the server and active org explicitly allow them, logging failures silently for debugging.

* fix(app): trace den-gated update checks

Wait for Den auth hydration before the startup update check and add debug logging around auth restoration and update gating so local-vs-cloud version decisions are visible while testing.

---------

Co-authored-by: src-opn <src-opn@users.noreply.github.com>
2026-04-21 12:01:25 -07:00
src-opn
e97a11d452 chore: bump version to 0.11.212 2026-04-20 18:48:25 -07:00
src-opn
ccdb46d111 chore: bump version to 0.11.211 2026-04-20 18:45:24 -07:00
Source Open
022b68a80c feat(app): key cloud providers by cloud id (#1510)
Use the raw cloud provider id for imported provider config and auth so synced removals and updates can reconcile against the same key. Preserve provider-family behavior by storing source metadata and using names where UI heuristics still need Anthropic/OpenAI detection.

Co-authored-by: src-opn <src-opn@users.noreply.github.com>
2026-04-20 18:38:11 -07:00
Source Open
aa8f39e3b8 feat(app): auto-sync cloud providers (#1509)
Keep workspace-managed providers aligned with the active cloud org on sign-in, launch, background sync, and Cloud settings open while declaring the sync interval in one place.

Co-authored-by: src-opn <src-opn@users.noreply.github.com>
2026-04-20 17:37:32 -07:00
Source Open
9462b41c60 feat(app): enforce desktop restriction policies (#1505)
* feat(app): enforce desktop restriction policies

Keep org desktop restrictions consistent in the desktop client by hiding blocked provider/model paths and syncing workspace config when Zen access is disabled.

* fix(app): persist provider disconnects in workspace config

Keep config-backed providers disabled through the same opencode.jsonc flow used by desktop restrictions so disconnecting OpenCode writes the workspace disabled_providers state reliably.

---------

Co-authored-by: src-opn <src-opn@users.noreply.github.com>
2026-04-20 15:33:54 -07:00
src-opn
872c21764c chore: bump version to 0.11.210 2026-04-20 11:49:19 -07:00
src-opn
f0e4f6db18 chore: bump version to 0.11.209 2026-04-20 10:58:35 -07:00
src-opn
3ac290fab7 chore: bump version to 0.11.208 2026-04-20 09:42:56 -07:00
Source Open
da9a4f24e9 feat(desktop): persist desktop bootstrap and org restrictions (#1479)
* feat(den-api): expose desktop config from env

* feat(desktop): persist den bootstrap config across updates

* feat(den): manage desktop restrictions per organization

* fix(app): stabilize cloud org selection

* docs(desktop): add bootstrap config PRD

---------

Co-authored-by: src-opn <src-opn@users.noreply.github.com>
2026-04-19 15:59:03 -07:00
Source Open
ac41d58b0b feat(den): use Better Auth active org context (#1485)
* feat(den): use Better Auth active org context

* fix(app): switch Better Auth org only on explicit actions

* refactor(den): flatten active org resource routes

---------

Co-authored-by: src-opn <src-opn@users.noreply.github.com>
2026-04-17 21:52:42 -07:00
Source Open
85ab73bcbe feat(den): gate desktop updates by supported version (#1476)
Co-authored-by: src-opn <src-opn@users.noreply.github.com>
2026-04-17 12:07:28 -07:00
ben
7bb7e5241d chore(deps): pin opencode CLI + SDK to v1.4.9 (#1471)
* chore(deps): pin opencode CLI and SDK to v1.4.9

- Bump constants.json opencodeVersion v1.2.27 -> v1.4.9 (CI source of
  truth consumed by ci-tests, build-desktop, alpha/prerelease/release
  workflows, and opencode-agents).
- Bump @opencode-ai/sdk across app, story-book, orchestrator,
  opencode-router, server-v2, and refresh the lockfile so all workspaces
  resolve to 1.4.9.

* refactor(app): adapt to @opencode-ai/sdk 1.4.9 type changes

- Move Model.reasoning reads to Model.capabilities.reasoning in
  model-config.ts and model-behavior.ts (SDK flattened capabilities).
- Simplify utils/providers.ts to a pass-through; Provider now carries
  source/options and Model.cost reshapes to {input, output, cache,
  experimentalOver200K}, which the mapper no longer needs to translate.
- Cast session.todo() result to TodoItem[] (SDK Todo has no id).
- Update server-v2 test fixtures from 1.2.27 to 1.4.9.
2026-04-17 11:35:09 -07:00
Jan Carbonell
e7d159e045 fix(i18n): restore share_skill_destination strings for en and fr (#1466)
The skill-destination modal shown when importing a shared skill rendered
raw translation keys (e.g. 'share_skill_destination.add_to_workspace')
for five strings in English and French. Those entries were stubbed with
their own keys as values in a previous i18n cleanup and never replaced
with real copy.

Restore proper English copy (reusing the wording already present for the
sibling '_desc' / 'confirm_button' / 'confirm_busy' keys that these
hint/CTA keys supersede) and mirror it in French. Other locales already
had real translations for these keys and are unaffected.
2026-04-16 16:51:38 -07:00
Pascal André
81997d13b7 fix(automations): clear stale scheduler install gate (#1461) 2026-04-16 10:24:06 -07:00
Jan Carbonell
809f221341 added docs redirect button inside the app (#1458) 2026-04-15 22:37:13 -07:00
Jan Carbonell
e232aba925 feat(i18n): add Catalan locale (#1450)
* feat(i18n): add Catalan locale

Give Catalan speakers full UI coverage while keeping locale parity with the English source and existing locale audits.

* fix(i18n): align Catalan product terminology

Use the requested in-product terms for workspace, paquet, and sandbox while keeping the Catalan locale consistent around runtime, host, and deep link wording.

* canvis de wording
2026-04-15 18:30:51 -07:00
Jan Carbonell
1871532e99 feat(i18n): add Spanish locale (#1451)
* feat(i18n): add Spanish locale

* chore(i18n): include Spanish in audit script

* fix(i18n): retune Spanish locale for Spain

* fix(i18n): update Spanish translations for clarity and consistency
2026-04-15 17:19:41 -07:00
ben
800602f4e3 feat: add microsandbox sandbox flow and feature flag toggle (#1446)
* add pre-baked microsandbox image

Bake openwork, openwork-server, and the pinned opencode binary into a single Docker image so micro-sandbox remote-connect smoke tests can boot quickly and be verified with curl and container health checks.

* add Rust microsandbox example

Add a standalone microsandbox SDK example that boots the OpenWork image, validates remote-connect endpoints, and streams sandbox logs so backend-only sandbox behavior can be exercised without Docker.

* exclude Rust example build output

Keep the standalone microsandbox example in git, but drop generated Cargo target artifacts so the branch only contains source, docs, and lockfile.

* test

* add microsandbox feature flag for sandbox creation

Made-with: Cursor

* refactor sandbox mode isolation

Made-with: Cursor
2026-04-15 15:10:52 -07:00
src-opn
7346d009bf chore: bump version to 0.11.207 2026-04-13 14:48:35 -07:00
Pascal André
2ffdf5db59 feat(i18n): add French locale (#1438)
* feat(i18n): add French locale

* chore(i18n): include French in audit script
2026-04-13 10:34:25 -07:00
Pascal André
bae127c182 fix(session): restore transcript autoscroll growth detection (#1439) 2026-04-12 17:20:51 -07:00
Source Open
1a0a5f27cb fix(composer): keep multiline drafts in sync after send (#1427)
Co-authored-by: src-opn <src-opn@users.noreply.github.com>
2026-04-10 16:38:37 -07:00
Jan Carbonell
5a994850c5 fix(cloud): keep dark settings controls readable (#1410)
Replace hard-coded light fills in the Cloud settings panel with design-system tokens so badges, pills, and outline controls keep readable contrast in dark mode.
2026-04-08 23:11:44 -07:00
Benjamin Shafii
5b9da8a8f7 Reapply "feat(app): build React session composer parity on Lexical (#1367)"
This reverts commit 2e29a21151.
2026-04-08 15:31:27 -07:00
Benjamin Shafii
2e29a21151 Revert "feat(app): build React session composer parity on Lexical (#1367)"
This reverts commit 9784c618bd.
2026-04-08 15:15:33 -07:00
ben
9784c618bd feat(app): build React session composer parity on Lexical (#1367)
* feat(app): add Lexical React composer shell

Replace the React session textarea with a Lexical-based plain-text composer shell so subsequent parity work can build on a real editor instead of continuing to extend the temporary textarea path.

* feat(app): route React composer through shared session draft flow

Make the React Lexical composer use the existing ComposerDraft send/draft pipeline from the session page and expose transcript copy plus model picker access so parity work builds on the same session actions as the Solid path.

* feat(app): add React composer controls and attachments

Extend the Lexical React composer with model/variant/agent controls, file attachments, prompt-vs-shell draft mode, and slash command suggestions while continuing to use the shared ComposerDraft send path from the existing session actions.

* feat(app): add React mention suggestions

Add end-of-input @ mention suggestions for agents and files in the Lexical React composer and serialize selected mentions into the shared ComposerDraft parts so the React path can target agent/file inputs instead of plain text only.

* feat(app): render React mentions as Lexical chips

Upgrade accepted React composer mentions from plain text into Lexical token nodes so agent and file selections render as real inline chips instead of raw @text while still serializing back into ComposerDraft parts.

* feat(app): add React composer notices and file paste/drop

Bring the Lexical React composer closer to the Solid UX by adding inline composer notices plus file paste/drop intake and attachment feedback without leaving the shared ComposerDraft pipeline.

* fix(app): make Lexical mention node serializable

Implement importJSON/exportJSON and constructor defaults for the React composer mention node so Lexical can safely instantiate and rehydrate accepted mention chips without throwing at runtime.

* feat(app): remove React shell composer mode

Drop the temporary Prompt/Shell toggle from the React session composer so the React path stays focused on prompt-mode parity instead of exposing an incomplete shell-mode branch.

* feat(app): add keyboard navigation for React composer menus

Add ArrowUp/ArrowDown, Enter/Tab accept, Escape close, and highlighted active-row state for the React slash and mention suggestion menus so the Lexical composer behaves more like the Solid composer.

* feat(app): make React mention chips behave like tokens

Treat accepted Lexical mention chips as real inline tokens by improving their visuals and adding backspace/arrow behavior so chip editing feels much closer to the Solid composer path.

* feat(app): add richer React paste and remote composer warnings

Extend the Lexical React composer with collapsed long-paste handling, unsupported-file link insertion, and remote/sandbox-specific notices so paste and drop behavior moves closer to the Solid composer path.

* feat(app): add per-message copy actions to React transcript

Add individual copy actions on React transcript messages so the React session surface closes another gap with the Solid path beyond the existing whole-transcript copy action.

* feat(app): render React slash commands as chips

Upgrade accepted slash commands in the Lexical React composer into real inline token nodes so slash completion feels more intentional and benefits from the same token navigation behavior as mentions.

* feat(app): polish React attachment cards and validation

Upgrade the React composer attachment UI with richer cards, image previews, mime badges, size display, and oversized-file warnings so attachment handling feels closer to the Solid composer path.

* feat(app): polish React composer menu interactions

Close the React mention/slash menus cleanly after selection and keep active-row state in sync with mouse hover so the Lexical composer menus feel less sticky and more intentional.

* feat(app): add pasted text chip UX to React composer

Promote collapsed pasted text into a first-class React composer surface with preview, copy, and remove actions so long pasted content is no longer only a hidden placeholder token inside the draft.

* feat(app): add React code and tool copy actions

Add copy affordances for Markdown code blocks plus tool request/result/diff sections so the React transcript and tool-call surfaces close more of the remaining practical parity gap with the Solid session UI.

* feat(app): polish React menu scrolling and upload actions

Keep the active React mention/slash selection scrolled into view and make remote-path warnings offer a more useful shared-folder upload action when attachments are already present in the composer.

* feat(app): align React drop behavior with attachment support

Handle unsupported dropped files more deliberately in the React composer by matching the attachment support matrix and surfacing an explanatory notice instead of silently treating every dropped file the same.

* feat(app): add keyboard navigation to React agent picker

Extend the React composer agent picker with active-row highlighting plus ArrowUp/ArrowDown/Enter/Escape behavior so it matches the rest of the keyboard-driven Lexical composer interactions.

* feat(app): anchor React composer to session bottom

Keep the React composer pinned to the bottom of the session layout with a dedicated footer container so the transcript scrolls above it instead of letting the composer drift upward when the session is sparse.

* feat(app): delay React session loading and switching UI

Only show the React session loading/switching affordances after a short delay and stop treating normal streaming as a switching state so the session surface feels calmer during quick transitions and active chats.

* feat(app): add visible React composer dropzone

Turn the React composer drag/drop path into a real dropzone with isolated local drag-over state and visible drop affordances instead of relying only on hidden drag handlers.

* fix(app): remove React composer glow

Drop the extra card shadow from the React composer container so the input surface stops showing the grey halo and sits closer to the cleaner Solid styling.

* fix(app): simplify React composer footer chrome

Remove the extra sticky footer treatment from the React session surface so the composer sits more cleanly in the layout without the added gradient wrapper.

* fix(app): clean up React transcript rendering

Remove the useless Step started label from the React transcript, render markdown horizontal rules as subtle dividers instead of heavy black lines, and tone down heading sizes so the session surface looks cleaner.

* fix(app): use light gray for React markdown dividers

Switch the horizontal rule color to a softer gray so markdown dividers feel like subtle section breaks instead of standing out.

* fix(app): clean up React composer layout, copy buttons, and pasted text chips

Fix the session layout so the transcript scrolls and the composer stays at the bottom. Replace raw Copy text buttons with cleaner icon-based copy affordances with check feedback. Render pasted multi-line text as Lexical inline chips instead of raw text in the editor.

* fix(app): unify React composer Run/Stop into one button

Replace the separate Stop and Run task buttons with a single button that switches between Run task and Stop based on streaming state so the composer footer stays clean.

* fix(app): remove Copy transcript button and fix user bubble spacing

Drop the standalone Copy transcript button and move per-message copy affordances to absolute positioning so they don't inflate message bubble height when hidden.

* fix(app): style React model variant picker as a proper menu

Replace the unstyled native select dropdown for model variant/behavior with a styled button + dropdown menu that matches the rest of the Lexical composer controls.

* fix(app): make React session mount chain full height

Add explicit full-height sizing through the Solid session wrapper and React island container so the React session surface can reliably fill the available vertical space without manual inline devtools overrides.

* feat(app): add React session scroll memory and auto-follow

Remember scroll position per session so switching between sessions restores where you left off. Scroll to bottom on first load of a new session. Auto-follow streaming text unless the user scrolls up. Show a scroll-to-bottom pill when not at the bottom.

* fix(app): prevent horizontal scroll in React session transcript

Add overflow-x-hidden to the transcript scroll container so wide content like code blocks or tool output cannot cause unwanted horizontal scrolling.

* fix(app): hide base64 data URLs in React file cards

Stop leaking raw data: URLs and application/octet-stream labels in file attachment cards. Show a clean filename or Attached file label instead, with a human-readable file type badge derived from the extension or mime type.

* feat(app): add React file card preview and desktop actions

Upgrade the React transcript file card with a cleaner design, proper file icon, and a desktop-only actions dropdown for opening files with the OS default app, revealing in Finder, or copying the path. Actions are hidden on web where Tauri APIs are not available.

* fix(app): parenthesize React variant label fallback

Use explicit parentheses around the nullish-coalescing variant label fallback so esbuild accepts the expression during production builds.
2026-04-08 15:13:07 -07:00
src-opn
135290004b chore: bump version to 0.11.206 2026-04-08 13:20:34 -07:00
Source Open
e27abd50c3 feat(den): add cloud skill uploads and sync tracking (#1400)
Co-authored-by: src-opn <src-opn@users.noreply.github.com>
2026-04-08 12:58:54 -07:00
Source Open
3e38da671a fix: remove HTML from reset confirmation translations (#1398)
Co-authored-by: src-opn <src-opn@users.noreply.github.com>
2026-04-08 10:49:24 -07:00
Johnny Shields
46fa5a22cb [READY] fix(i18n): Add more missed translations + fixes + cleanup (#1381)
* Add more missed translations for all locales

* Add missing translations

* Remove unused keys

* Sort translation keys alphabetically

* Extra polish

* Fix broken translations

* Last fix

* Fix dangling references

* More translations, improve audit script

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 10:44:18 -07:00
Jan Carbonell
da5d972a54 fix(providers): hide anthropic browser auth (#1395) 2026-04-08 10:18:03 -07:00
src-opn
6b26f7765f chore: bump version to 0.11.205 2026-04-07 17:55:49 -07:00
Source Open
e6bb4b2903 feat(den): add cloud provider and skill hub imports (#1394)
Co-authored-by: src-opn <src-opn@users.noreply.github.com>
2026-04-07 17:53:07 -07:00
src-opn
eee4a847de chore: bump version to 0.11.204 2026-04-07 17:41:38 -07:00
Source Open
7e82cb7253 fix(den): refresh desktop sign-in state and support local handoff (#1389)
* fix(den): make desktop signin state update immediately

* fix(den): support local auth verification and handoff

---------

Co-authored-by: src-opn <src-opn@users.noreply.github.com>
2026-04-07 16:28:52 -07:00
Source Open
60c7e87eaa fix(workspace): show mounted url in edit connection (#1388)
Co-authored-by: src-opn <src-opn@users.noreply.github.com>
2026-04-07 13:44:27 -07:00
Source Open
d1b23efea0 fix(composer): stabilize session input sizing (#1387)
Co-authored-by: src-opn <src-opn@users.noreply.github.com>
2026-04-07 13:38:53 -07:00
Source Open
91ffa71cf9 fix(settings): correct ready-to-install update label (#1384)
Co-authored-by: src-opn <src-opn@users.noreply.github.com>
2026-04-07 12:01:18 -07:00
src-opn
d5459798b2 chore: bump version to 0.11.203 2026-04-07 10:49:19 -07:00
Jan Carbonell
a16f35bc12 patch so anthropic oauth doesn't show up (#1378) 2026-04-07 01:07:43 -06:00
Source Open
95f53dfa32 feat(app): add composer tools popover (#1369)
* feat(app): add composer tools popover

* fix(app): highlight connected MCP badges

---------

Co-authored-by: src-opn <src-opn@users.noreply.github.com>
2026-04-06 13:52:50 -07:00
Source Open
0589897b2f feat(den): add org-managed llm provider library (#1343)
* feat(den): add org-managed llm provider library

Let Den admins curate shared providers and models with encrypted credentials, then let the app connect through the existing add-provider flow. This keeps org-wide model access consistent without requiring per-user OAuth setup.

* docs(den): prefer longer db encryption keys

* fix(den): pass db encryption key through local dev

---------

Co-authored-by: src-opn <src-opn@users.noreply.github.com>
2026-04-06 10:17:21 -07:00
ben
b3afb8a176 fix(app): keep React session transcript stable across switches (#1365)
* feat(app): add React markdown and transcript parity

Render React session messages with a proper markdown stack, Solid-style user and assistant layouts, and structured tool-call cards so the React path matches the existing transcript experience more closely.

* feat(app): drive React sessions through useChat streaming

Move the React session path to a custom useChat transport backed by the existing OpenCode event stream so transcript state, sending, and streaming are owned by React instead of snapshot polling.

* fix(app): remount React session island per chat

Reset the React session surface when the workspace or session changes so an in-flight useChat stream from one chat cannot continue writing into another chat after a switch.

* fix(app): scope React stream deltas to one session

Reject message.part.delta updates that do not belong to the active session so concurrent OpenCode event streams cannot interleave transcript text across chats in the React session surface.

* fix(app): cache React transcript state per session

Persist streamed UI messages in TanStack query cache per workspace/session so revisiting a running chat shows the last known partial transcript instead of starting empty on remount.

* feat(app): keep React session state in shared sync cache

Move React session transcript ownership out of the mounted chat view by sharing one query client across islands and applying workspace event updates into per-session query cache, mirroring the global sync pattern used by OpenCode.

* fix(app): keep React session sync alive across chat switches

Mount the workspace-scoped React session sync above the per-chat session island so switching chats no longer tears down the event subscription that keeps transcript state updating in the background.

* Revert "fix(app): keep React session sync alive across chat switches"

This reverts commit 15f37a09c1.

* Revert "feat(app): keep React session state in shared sync cache"

This reverts commit 49df59d6ef.

* fix(app): keep React session streams alive across switches

Move React session streaming state into a shared app-level runtime backed by one query client and a workspace-scoped event sync reducer so leaving and returning to a running session restores the current transcript instead of showing an empty pane or leaking between chats.

* fix(app): preserve beginning of streamed text across session switches

Create text parts on first delta arrival instead of silently dropping deltas that arrive before the part shell exists, and ensure the message shell is present before appending any delta. This fixes the bug where switching away from a streaming session and coming back would show only later lines, missing the beginning of the response.

* fix(app): preserve streamed text when re-selecting a busy session

When switching back to a session that is still streaming, the server snapshot returns empty text for in-progress parts. The reconcile call in selectSession was overwriting the locally accumulated text with that empty snapshot. Now both the Solid and React paths preserve the longer local text when the session status is busy.
2026-04-06 08:13:05 -07:00
ben
dfb7f1b2dc fix(startup): move boot orchestration to TS and cut desktop sync stalls (#1366)
Shift startup/loading control to TS with explicit boot phases, cached->live sidebar transitions, and startup trace markers while reducing Rust-side blocking in workspace and orchestrator status paths to improve macOS responsiveness during boot and workspace switching.

Made-with: Cursor
2026-04-06 07:44:34 -07:00
ben
9365e7d397 feat(app): add incremental React session path (#1362)
* feat(server): add workspace session read APIs

Expose workspace-scoped session list, detail, message, and snapshot reads so the client can fetch session data without depending on activation choreography.

* feat(app): route mounted session reads through OpenWork APIs

Use the new workspace-scoped session read endpoints for mounted OpenWork clients so the current frontend stops depending on direct session proxy reads for list, detail, message, and todo loading.

* feat(app): add React read-only session transcript

Introduce a feature-gated React island for the session transcript so we can replace the session surface incrementally while keeping the Solid shell intact.

* feat(app): add React session composer surface

Extend the feature-gated React session island to own its draft, prompt send, stop flow, and snapshot polling so the session body can evolve independently from the Solid composer.

* feat(app): add React session transition model

Keep the React session surface stable during session switches by tracking rendered vs intended session state and exposing a developer debug panel for render-source and transition inspection.

* docs(prd): add React migration plan to repo

Copy the incremental React adoption PRD into the OpenWork repo so the migration plan lives next to the implementation and PR branch.

* docs(prd): sync full React migration plan

Replace the shortened repo copy with the full incremental React adoption PRD so the implementation branch and product plan stay in sync.

* feat(desktop): add React session launch modes

Add dedicated Tauri dev and debug-build entrypoints for the React session path and honor a build-time React session flag before local storage so the alternate shell is easy to launch and reproduce.

* fix(app): fall back to legacy mounted session reads

Keep the new app working against older OpenWork servers by falling back to the original mounted OpenCode session reads when the workspace-scoped session read APIs are unavailable.
2026-04-05 16:46:06 -07:00
ben
2b740531a4 fix(app): remove more dead settings shell code (#1350)
* fix(app): remove more dead settings shell code

* fix(app): prune more dead helpers and props
2026-04-04 17:56:43 -07:00
ben
6827c5670e fix(app): remove dead shell and session rail code (#1347) 2026-04-04 14:25:19 -07:00
Jan Carbonell
cdfc4eca8b removing unused functions (#1346)
* removing unused functions

* also not needed (from landing page)

* added readme on script
2026-04-04 14:48:53 -06:00