diff --git a/CHANGELOG.md b/CHANGELOG.md index 751d1871d5..54c34aa5a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -507,16 +507,17 @@ already in use` and no way forward short of finding the file in a temporary gateway that cannot be reached is never treated as a bad key, so a flaky network will not sign you out. ([#296](https://github.com/quickbeard/codev-code/pull/296)) -- **The Work tabs stay out of the way until you use sandboxes** — every desktop - install showed a **Code** / **Work** switch above the projects list, even - though Work was empty for anyone who had never made a sandbox: switching to it - offered to build one and otherwise had nothing to show. The switch now appears - once you actually have a sandbox — created from Settings → Servers → **+** → - **Add sandbox** — and Work behaves exactly as before from then on. Delete your - last sandbox and it steps back out of the way again. If you want Work visible - before creating anything, Settings → General → Advanced has a **Show Work** - switch. - ([#292](https://github.com/quickbeard/codev-code/pull/292)) +- **You can hide the Work tabs if you do not use sandboxes** — the **Code** / + **Work** switch above the projects list is empty for anyone who has never made + a sandbox: switching to Work offers to build one and otherwise has nothing to + show. Settings → General → Advanced now has a **Show Work** switch. Turn it + off and both the switch and the Work side stay out of the way until you make a + sandbox — from Settings → Servers → **+** → **Add sandbox** — after which Work + behaves exactly as before. Delete your last sandbox and it steps back out of + the way again. The switch is on by default, so nothing moves unless you ask + it to. + ([#292](https://github.com/quickbeard/codev-code/pull/292), + [#318](https://github.com/quickbeard/codev-code/pull/318)) - **Files first in the `@` mention menu** — the composer's `@` popup listed references, agents, and MCP resources ahead of files, so the top match Enter would accept was rarely the file being typed. File results (recent open tabs diff --git a/DIVERGENCES.md b/DIVERGENCES.md index e6f1c76170..d0ad1ce1a5 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -290,7 +290,7 @@ from the squash message first, or use a plain merge. | `packages/session-ui/src/v2/components/prompt-input/interaction.ts` | The `@` mention popup's `contextList` grouping delegates to the fork-only `at-options.ts` beside it (`atSuggestionCategory` + `compareAtCategories`, unit-tested in the fork-only `at-options.test.ts`): suggestion groups rank recent → file → reference → agent → resource instead of upstream's reference → agent → resource → recent → file, so file results lead in the V2 composer — the prompt the desktop and web apps render now that the legacy layout is retired. The grouped menu scrolls, so no row-reserve logic is needed (unlike the TUI/run-footer variants), and default selection stays index 0, so Enter accepts the top file match. `contextList`'s resolver also searches on an empty query — it used to return the fixed entries plus open tabs without asking, so a bare `@` listed no files at all and the ordering above had nothing to order (the same empty-query gap the TUI and run-footer fixed in the `@` autocomplete row above). Covered by the fork-only `packages/app/test-browser/prompt-at-menu.test.ts` and the e2e `packages/app/e2e/regression/prompt-at-menu-files.spec.ts`. | Files are the dominant `@` use case; the earlier files-first reorder (see the `@` autocomplete row above) covered the TUI, the legacy web prompt, and the `codev run` footer but missed the V2 composer. | Keep the two-line delegation to `at-options.ts`; take upstream's surrounding changes. If upstream restructures the suggestion list, re-apply the ranking wherever context suggestions are grouped. | | Legacy prompt input removed: deleted `packages/app/src/components/prompt-input.tsx` and its now-orphaned submodules `packages/app/src/components/prompt-input/{attachments,files,paste,transient-state}.ts`, `{context-items,drag-overlay,image-attachments,slash-popover}.tsx`, `image-attachments.css`, `attachments.test.ts`, `packages/app/test-browser/{prompt-attachments,prompt-transient-state,prompt-at-options}.test.ts` (plus the fork-only `at-options{,.test}.ts`); edits in `packages/app/src/pages/session.tsx`, `packages/app/src/pages/session/composer/{session-composer-region-controller.ts,todo-panel-motion.stories.tsx}` | The fork hardcodes `newLayoutDesigns` to `true` (see the settings.tsx row), so the legacy `PromptInput` — the `fallback` of session.tsx's `` — could never render. session.tsx now creates the V2 controller unconditionally and always mounts `PromptInputV2Composer` (no `Show`); `session-composer-region-controller.ts` imports `PromptInputState` from `prompt-input/contracts` (where it is defined) instead of the deleted barrel; the todo-panel-motion story fills its composer-region `promptInput` slot with a static styled placeholder instead of the legacy component (its unused `controls` mock removed). Kept: `prompt-input/{contracts,history,history-store,placeholder,submit,editor-dom,build-request-parts,submission-state}.ts` and their tests — the V2 composer path still uses them. | Dead code: retired with the legacy layout, kept compiling for months, and PR #56-style fixes had to be applied to it for nothing. | Keep deleted — `git rm` the upstream side of modify/delete conflicts on these files, and delete any new module upstream adds under `prompt-input/` that only the legacy `PromptInput` imports. Take upstream changes to the kept shared modules. If upstream restructures session.tsx's composer block, re-apply "always render the V2 composer" and keep the legacy fallback out; if upstream rewires the story's input slot, keep our placeholder. If the fork ever wants the legacy layout back, restore from upstream, not from history. | -| Work side is opt-in: `packages/app/src/context/settings.tsx`, `packages/app/src/components/settings-v2/general.tsx`, `packages/app/src/i18n/{en,es}.ts` | The Work side and the Code/Work tab strip render only when `workSideAvailable()` (fork-only `packages/app/src/workspace/side.ts`) says so: sandbox-capable platform AND (a sandbox exists OR the new `general.showWork` preference is on). settings.tsx gains `showWork` (default `false`) with its accessor/setter beside `showCustomAgents`; settings-v2's Advanced section gains a "Show Work" switch, wrapped in `` so non-sandbox builds never offer it; i18n gains `settings.general.row.showWork.{title,description}`. The fork-only wiring is in `workspace/tabs.tsx`: a `useWorkAvailable()` hook feeds both `useWorkspaceSide()`'s `effectiveSide` call and `WorkspaceTabs`'s `` (which previously gated on `platform.sandboxes`), so the side and its strip can never disagree — a hidden strip with Work selected would strand the user on sandbox servers with no way back. An undefined sandbox list means "not loaded", not "none", and keeps Work (the same race the Code/Work split row calls out); the main process answers `sandbox-get-state` synchronously, so that window is sub-frame. Tests: `packages/app/test-browser/workspace-side.test.ts` covers the four `workSideAvailable` branches. | Sandboxes are a heavyweight opt-in feature (a VM per sandbox), but every desktop profile paid for them in the sidebar: a Code/Work strip with a Work side that had nothing in it and offered to build one. Hiding both until a sandbox exists makes the strip appear exactly when there are two sides worth picking between; the preference is the discoverable way in for someone who wants Work before creating anything, and Settings → Servers → + → Add sandbox is the other. Owning a sandbox shows Work whatever the preference says. | Keep the `workSideAvailable` gate on both `useWorkspaceSide` and `WorkspaceTabs` — gating only one reintroduces the stranded-side bug. Take upstream's surrounding settings/i18n changes. If upstream adds its own sandbox-visibility preference, prefer theirs and drop `showWork`. Never collapse on an unloaded sandbox list. | +| Work side can be hidden: `packages/app/src/context/settings.tsx`, `packages/app/src/components/settings-v2/general.tsx`, `packages/app/src/i18n/{en,es,vi}.ts` | The Work side and the Code/Work tab strip render only when `workSideAvailable()` (fork-only `packages/app/src/workspace/side.ts`) says so: sandbox-capable platform AND (a sandbox exists OR the new `general.showWork` preference is on). settings.tsx gains `showWork` (default `true`) with its accessor/setter beside `showCustomAgents`; settings-v2's Advanced section gains a "Show Work" switch, wrapped in `` so non-sandbox builds never offer it; i18n gains `settings.general.row.showWork.{title,description}`. The fork-only wiring is in `workspace/tabs.tsx`: a `useWorkAvailable()` hook feeds both `useWorkspaceSide()`'s `effectiveSide` call and `WorkspaceTabs`'s `` (which previously gated on `platform.sandboxes`), so the side and its strip can never disagree — a hidden strip with Work selected would strand the user on sandbox servers with no way back. An undefined sandbox list means "not loaded", not "none", and keeps Work (the same race the Code/Work split row calls out); the main process answers `sandbox-get-state` synchronously, so that window is sub-frame. Tests: `packages/app/test-browser/workspace-side.test.ts` covers the four `workSideAvailable` branches. | Sandboxes are a heavyweight opt-in feature (a VM per sandbox), and a desktop profile that does not use them pays for the strip anyway: a Work side with nothing in it that offers to build one. The gate lets that profile put both away. It ships **on** rather than off, though — the default was flipped in #318 before either shipped. Hiding Work until a sandbox exists also hides the only evidence that sandboxes exist, so the profile most likely to have the strip taken away is the one that would then never discover the feature; and it silently changes the sidebar for every existing install. On by default keeps the app as it is and makes putting Work away an explicit choice. Owning a sandbox shows Work whatever the preference says. | Keep the `workSideAvailable` gate on both `useWorkspaceSide` and `WorkspaceTabs` — gating only one reintroduces the stranded-side bug. Take upstream's surrounding settings/i18n changes. If upstream adds its own sandbox-visibility preference, prefer theirs and drop `showWork`. Never collapse on an unloaded sandbox list. Keep the default `true`: with it `false`, `workSideAvailable` is load-bearing on every launch, and a profile with no sandboxes loses the strip without having asked. | | `packages/ui/src/context/dialog.tsx` (again) | The `Kobalte.Overlay` lost its `onClick`, so a backdrop click no longer closes the top dialog. Escape (the window `keydown` listener) and `onOpenChange` (the close button) are untouched, as is the `persistent` guard on both. | A stray click in the dimmed area around a dialog dismissed it and discarded whatever it held — a half-filled sandbox form, a provider mid-connect. Dismissal is now always deliberate: the corner close button or Escape. | Keep the overlay handler-less. If upstream adds behaviour to the overlay, take it minus the close. Pairs with the `onInteractOutside` guards on the three dialog contents below — removing the handler alone is not enough, Kobalte's own dismissable layer closes on outside pointerdown too. | | Dialogs close on a button, never on an outside click: `packages/ui/src/components/dialog.tsx`, `packages/ui/src/components/dialog.css`, `packages/ui/src/v2/components/dialog-v2.tsx`, `packages/ui/src/v2/components/dialog-v2.css`, `packages/ui/src/components/image-preview.tsx`, plus the `*.stories.tsx` beside the two dialogs | All three `Kobalte.Content`s take `onInteractOutside={(e) => e.preventDefault()}`, which is what actually blocks Kobalte's dismissable layer (the context row above only removes the explicit overlay click). Both `Dialog`s gained `closeLabel?`/`hideClose?` and now render the `Kobalte.CloseButton` themselves, as the last child of their content, absolutely positioned at the content's top-right — v1 at 16px/20px (12px/12px when `data-no-header`), v2 at 14px/14px, both at `z-index: 30` so they clear the settings tab header's sticky `z-index: 10`. The button was removed from v2's `DialogHeader` (which loses its `hideClose`/`closeLabel` props and its `data-hide-close` attribute) and from v1's header `Switch`, whose `action` branch used to *replace* it; `action` now simply renders. Headers reserve the corner in `padding-right` (v1 52px, v2 42px) and v1's title takes `flex: 1`; both containers take a `min-height` (v1 52px, v2 48px) so a short `fit` dialog cannot clip the button. `aria-label` defaults to `ui.common.close` through `useI18n` — the first v2 component to use the UI i18n context, which has an English fallback. | Every dialog needed a visible way out once the backdrop stopped being one, including the dozen whose call sites passed `hideClose`. Pinning the button to the content corner rather than laying it out in the header is what lets header-less dialogs (settings, release notes, the command palettes) have one at all; keeping it inside `Kobalte.Content` keeps it inside the focus trap and the modal's pointer-events layer. | Keep the corner button and the `onInteractOutside` guards. Upstream owns the header/body/footer layout — take its changes and re-apply the `padding-right` reserve and the close button on top. If upstream adds its own close button to `DialogHeader`, drop ours from `Dialog` rather than shipping two. | | Dialog close buttons at the call sites: `packages/app/src/sandbox/{dialog-add,dialog-add-default,dialog-delete,dialog-mounts}.tsx`, `packages/app/src/wsl/{dialog-add-server,dialog-delete}.tsx`, `packages/app/src/components/{dialog-manage-models,dialog-connect-provider,dialog-select-model-unpaid-v2,session-actions-menu,settings-v2/dialog-server-v2}.tsx`, `packages/app/src/components/gateway/dialog-configure.tsx`, `packages/app/src/components/dialog-select-file.tsx`, `packages/app/src/components/dialog-command-palette-v2.css` | Dropped every `hideClose`/`closeLabel` prop from `DialogHeader` (the button moved to `Dialog`, and hiding it is no longer wanted anywhere it was hidden). `dialog-configure` passes `hideClose` on its `Dialog` instead — it is the configure gate, which the dialog stack also marks `persistent`, so a close button there would be inert. The two search-first dialogs reserve the corner the button now occupies: `dialog-select-file` via `[&_[data-slot=list-search-wrapper]]:pr-10`, the v2 command palette via `padding-right` on `.command-palette-v2-search`. | Fork-only sandbox/WSL/gateway dialogs aside, these are upstream files whose only change is a removed prop. | Take upstream's version of these call sites and re-drop `hideClose`. The `hideClose` on `dialog-configure` is fork-only and must stay. | diff --git a/packages/app/src/components/settings-v2/general.tsx b/packages/app/src/components/settings-v2/general.tsx index c40f6d2f7f..ab5c1bfbff 100644 --- a/packages/app/src/components/settings-v2/general.tsx +++ b/packages/app/src/components/settings-v2/general.tsx @@ -368,11 +368,11 @@ export const SettingsGeneralV2: Component<{ - {/* Fork: sandboxes are opt-in, so this reveals the Work side before the - first one exists. Owning a sandbox shows Work regardless, which is - why the row says "even with no sandboxes" rather than promising to - hide it. Sandbox-capable builds only — elsewhere there is no Work - side to reveal. */} + {/* Fork: on by default, so the meaningful action is turning it off — + that hides the Work side and its strip until a sandbox exists. + Owning a sandbox shows Work regardless, which is why the row + promises to keep Work visible rather than to hide it. Sandbox-capable + builds only — elsewhere there is no Work side either way. */}