From c589fc1aaaad33ea6e490100903b57f6d27d6bc6 Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Tue, 7 Apr 2026 06:13:50 -0500 Subject: [PATCH 1/3] chore(release): prepare v0.18.0 --- CHANGELOG.md | 29 ++++++++ apps/desktop/package.json | 2 +- apps/desktop/src/main.ts | 11 ++- apps/mobile/android/app/build.gradle | 2 +- .../ios/App/App.xcodeproj/project.pbxproj | 4 +- apps/mobile/package.json | 2 +- apps/server/package.json | 2 +- apps/web/package.json | 2 +- apps/web/src/components/ChatView.tsx | 10 ++- .../src/components/PreviewLayoutSwitcher.tsx | 12 +-- apps/web/src/components/PreviewPanel.test.ts | 6 +- apps/web/src/components/PreviewPanel.tsx | 26 ++++--- apps/web/src/components/Sidebar.tsx | 4 +- apps/web/src/components/chat/ChatHeader.tsx | 8 +- .../src/components/chat/InlineDiffBlock.tsx | 4 +- apps/web/src/hooks/useLayoutActions.ts | 8 ++ apps/web/src/previewStateStore.test.ts | 6 +- docs/releases/README.md | 1 + docs/releases/v0.18.0.md | 67 +++++++++++++++++ docs/releases/v0.18.0/assets.md | 73 +++++++++++++++++++ docs/releases/v0.18.0/rollout-checklist.md | 64 ++++++++++++++++ docs/releases/v0.18.0/soak-test-plan.md | 38 ++++++++++ packages/contracts/package.json | 2 +- 23 files changed, 325 insertions(+), 58 deletions(-) create mode 100644 docs/releases/v0.18.0.md create mode 100644 docs/releases/v0.18.0/assets.md create mode 100644 docs/releases/v0.18.0/rollout-checklist.md create mode 100644 docs/releases/v0.18.0/soak-test-plan.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 10d93b42..d4ab30ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.18.0] - 2026-04-07 + +See [docs/releases/v0.18.0.md](docs/releases/v0.18.0.md) for full notes and [docs/releases/v0.18.0/assets.md](docs/releases/v0.18.0/assets.md) for release asset inventory. + +### Added + +- Add a unified workspace panel to the right sidebar. +- Add bulk delete-all worktree cleanup action. +- Add preview layout modes and pop-out controls. +- Add project sidebar expand-all toggle. +- Add tab snapshot capture for the browser preview. +- Add support for binary project writes. + +### Changed + +- Make CLI publishing optional in the coordinated release workflow. +- Open diff files in the integrated viewer by default. +- Cache sidebar thread and project lookups for faster navigation. +- Lock the browser preview to the top dock. +- Collapse the workspace tree when binary project writes are shown. +- Move sidebar branding to the footer and drop the chat project badge. +- Clean up chat header panel toggles. + +### Fixed + +- Reduce preview and diff flicker during active workspace updates. +- Resolve web release warnings before shipping v0.18.0. + ## [0.17.0] - 2026-04-07 See [docs/releases/v0.17.0.md](docs/releases/v0.17.0.md) for full notes and [docs/releases/v0.17.0/assets.md](docs/releases/v0.17.0/assets.md) for release asset inventory. @@ -552,3 +580,4 @@ First public version tag. See [docs/releases/v0.0.1.md](docs/releases/v0.0.1.md) [0.13.0]: https://github.com/OpenKnots/okcode/releases/tag/v0.13.0 [0.16.1]: https://github.com/OpenKnots/okcode/releases/tag/v0.16.1 [0.17.0]: https://github.com/OpenKnots/okcode/releases/tag/v0.17.0 +[0.18.0]: https://github.com/OpenKnots/okcode/releases/tag/v0.18.0 diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 632a92b3..d9aad469 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@okcode/desktop", - "version": "0.17.0", + "version": "0.18.0", "private": true, "main": "dist-electron/main.js", "scripts": { diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 911fe649..baf860d8 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -1447,7 +1447,7 @@ function registerIpcHandlers(): void { }); // Fill the pop-out window with the preview surface. - const [popOutWidth, popOutHeight] = popOut.getContentSize(); + const [popOutWidth = 0, popOutHeight = 0] = popOut.getContentSize(); popOutController.setBounds({ x: 0, y: 0, @@ -1461,7 +1461,7 @@ function registerIpcHandlers(): void { // Keep preview surface filling the pop-out window on resize. popOut.on("resize", () => { if (popOut.isDestroyed()) return; - const [w, h] = popOut.getContentSize(); + const [w = 0, h = 0] = popOut.getContentSize(); popOutController.setBounds({ x: 0, y: 0, @@ -1488,10 +1488,9 @@ function registerIpcHandlers(): void { if (isDevelopment && process.env.VITE_DEV_SERVER_URL) { void popOut.loadURL(`${process.env.VITE_DEV_SERVER_URL}?popout=true`); } else { - void popOut.loadFile( - Path.join(__dirname, "../../web/dist/index.html"), - { query: { popout: "true" } }, - ); + void popOut.loadFile(Path.join(__dirname, "../../web/dist/index.html"), { + query: { popout: "true" }, + }); } }); diff --git a/apps/mobile/android/app/build.gradle b/apps/mobile/android/app/build.gradle index 804cad6c..34858829 100644 --- a/apps/mobile/android/app/build.gradle +++ b/apps/mobile/android/app/build.gradle @@ -8,7 +8,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 - versionName "0.17.0" + versionName "0.18.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/apps/mobile/ios/App/App.xcodeproj/project.pbxproj b/apps/mobile/ios/App/App.xcodeproj/project.pbxproj index b34d366b..1aec80ad 100644 --- a/apps/mobile/ios/App/App.xcodeproj/project.pbxproj +++ b/apps/mobile/ios/App/App.xcodeproj/project.pbxproj @@ -303,7 +303,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.17.0; + MARKETING_VERSION = 0.18.0; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = com.openknots.okcode.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -325,7 +325,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.17.0; + MARKETING_VERSION = 0.18.0; PRODUCT_BUNDLE_IDENTIFIER = com.openknots.okcode.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 221efd39..ac8d41b9 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@okcode/mobile", - "version": "0.17.0", + "version": "0.18.0", "private": true, "type": "module", "scripts": { diff --git a/apps/server/package.json b/apps/server/package.json index 3b8ddbda..dcb2bb60 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "okcodes", - "version": "0.17.0", + "version": "0.18.0", "license": "MIT", "repository": { "type": "git", diff --git a/apps/web/package.json b/apps/web/package.json index 348c837d..d6d6d073 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@okcode/web", - "version": "0.17.0", + "version": "0.18.0", "private": true, "type": "module", "scripts": { diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index ad7e9018..91658350 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -423,8 +423,13 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) { const previewDock = usePreviewStateStore((state) => activeProjectId ? (state.dockByProjectId[activeProjectId] ?? "top") : "top", ); + const previewLayoutMode = usePreviewStateStore((state) => + activeProjectId ? (state.layoutModeByProjectId[activeProjectId] ?? "top") : "top", + ); const previewSizeDefault = - previewLayoutMode === "side" ? PREVIEW_SPLIT_SIDE_DEFAULT_SIZE_PX : PREVIEW_SPLIT_DEFAULT_SIZE_PX; + previewLayoutMode === "side" + ? PREVIEW_SPLIT_SIDE_DEFAULT_SIZE_PX + : PREVIEW_SPLIT_DEFAULT_SIZE_PX; const previewSize = usePreviewStateStore((state) => activeProjectId ? (state.sizeByProjectId[activeProjectId] ?? previewSizeDefault) @@ -432,9 +437,6 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) { ); const togglePreviewLayout = usePreviewStateStore((state) => state.toggleProjectLayout); const setPreviewSize = usePreviewStateStore((state) => state.setProjectSize); - const previewLayoutMode = usePreviewStateStore((state) => - activeProjectId ? (state.layoutModeByProjectId[activeProjectId] ?? "top") : "top", - ); const previewSplitRef = useRef(null); const previewResizeStateRef = useRef<{ pointerId: number; diff --git a/apps/web/src/components/PreviewLayoutSwitcher.tsx b/apps/web/src/components/PreviewLayoutSwitcher.tsx index c10cd9bf..8c8e2f61 100644 --- a/apps/web/src/components/PreviewLayoutSwitcher.tsx +++ b/apps/web/src/components/PreviewLayoutSwitcher.tsx @@ -1,17 +1,9 @@ import type { ProjectId } from "@okcode/contracts"; -import { - Maximize2Icon, - PanelRightIcon, - PanelTopIcon, - PictureInPicture2Icon, -} from "lucide-react"; +import { Maximize2Icon, PanelRightIcon, PanelTopIcon, PictureInPicture2Icon } from "lucide-react"; import { readDesktopPreviewBridge } from "~/desktopPreview"; import { cn } from "~/lib/utils"; -import { - type PreviewLayoutMode, - usePreviewStateStore, -} from "~/previewStateStore"; +import { type PreviewLayoutMode, usePreviewStateStore } from "~/previewStateStore"; import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; diff --git a/apps/web/src/components/PreviewPanel.test.ts b/apps/web/src/components/PreviewPanel.test.ts index 5d7b3f8e..5d0979fa 100644 --- a/apps/web/src/components/PreviewPanel.test.ts +++ b/apps/web/src/components/PreviewPanel.test.ts @@ -46,11 +46,7 @@ describe("PreviewLayoutSwitcher", () => { }); describe("previewStateStore layout mode", () => { - const projectId = "test-project" as unknown as Parameters< - typeof usePreviewStateStore.getState - >[0] extends undefined - ? string - : string; + const projectId = "test-project"; it("defaults layout mode to 'top'", () => { const state = usePreviewStateStore.getState(); diff --git a/apps/web/src/components/PreviewPanel.tsx b/apps/web/src/components/PreviewPanel.tsx index 5b80a822..ac867873 100644 --- a/apps/web/src/components/PreviewPanel.tsx +++ b/apps/web/src/components/PreviewPanel.tsx @@ -635,18 +635,20 @@ export function PreviewPanel({ projectId, threadId, onClose }: PreviewPanelProps {/* Exit fullscreen shortcut button */} {layoutMode === "fullscreen" && ( - - - + toggleFullscreen(projectId)} + > + + + } + /> Exit full screen diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 8d6e9d6c..22675e23 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -1649,9 +1649,7 @@ export default function Sidebar() { }); }, []); - const wordmark = ( - - ); + const wordmark = ; return ( <> diff --git a/apps/web/src/components/chat/ChatHeader.tsx b/apps/web/src/components/chat/ChatHeader.tsx index 4e340963..6724a20c 100644 --- a/apps/web/src/components/chat/ChatHeader.tsx +++ b/apps/web/src/components/chat/ChatHeader.tsx @@ -7,19 +7,16 @@ import type { import { useQuery } from "@tanstack/react-query"; import { ChevronDownIcon, ExternalLinkIcon, GitPullRequestIcon } from "lucide-react"; import { memo, useCallback, useEffect } from "react"; -import { useTheme } from "~/hooks/useTheme"; import { useThreadTitleEditor } from "~/hooks/useThreadTitleEditor"; import { shortcutLabelsForCommand } from "~/keybindings"; import type { ClientMode } from "~/lib/clientMode"; import { gitStatusQueryOptions } from "~/lib/gitReactQuery"; import { ensureNativeApi } from "~/nativeApi"; import type { PreviewDock } from "~/previewStateStore"; -import { useProjectColor } from "~/projectColors"; import type { ProjectScriptDraft } from "~/projectScriptDefaults"; import { EditableThreadTitle } from "../EditableThreadTitle"; import GitActionsControl from "../GitActionsControl"; import ProjectScriptsControl, { type NewProjectScriptInput } from "../ProjectScriptsControl"; -import { Badge } from "../ui/badge"; import { Button } from "../ui/button"; import { Kbd } from "../ui/kbd"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; @@ -61,7 +58,7 @@ interface ChatHeaderProps { export const ChatHeader = memo(function ChatHeader({ activeThreadId, activeThreadTitle, - activeProjectId, + activeProjectId: _activeProjectId, activeProjectName, activeProjectCwd, isLocalDraftThread, @@ -91,9 +88,6 @@ export const ChatHeader = memo(function ChatHeader({ onMinimize, }: ChatHeaderProps) { const isMobileCompanion = clientMode === "mobile"; - const projectColor = useProjectColor(activeProjectId); - const { resolvedTheme } = useTheme(); - const isDark = resolvedTheme === "dark"; const { editingThreadId, draftTitle, diff --git a/apps/web/src/components/chat/InlineDiffBlock.tsx b/apps/web/src/components/chat/InlineDiffBlock.tsx index 75d46aee..e21973eb 100644 --- a/apps/web/src/components/chat/InlineDiffBlock.tsx +++ b/apps/web/src/components/chat/InlineDiffBlock.tsx @@ -171,7 +171,7 @@ function basename(filePath: string): string { /** Max lines to show before collapsing with a "show more" toggle. */ const MAX_VISIBLE_LINES = 18; -function DiffLineRow(props: { line: DiffLine; html?: string }) { +function DiffLineRow(props: { line: DiffLine; html?: string | undefined }) { const { line, html } = props; return ( @@ -202,7 +202,7 @@ function DiffLineRow(props: { line: DiffLine; html?: string }) { /** Shared diff-lines rendering used by both the main path and the error * boundary fallback, eliminating the previous triplication of JSX. */ function DiffLinesContent(props: { - lines: Array<{ key: string; line: DiffLine; html?: string }>; + lines: Array<{ key: string; line: DiffLine; html?: string | undefined }>; needsTruncation: boolean; hiddenCount: number; isExpanded: boolean; diff --git a/apps/web/src/hooks/useLayoutActions.ts b/apps/web/src/hooks/useLayoutActions.ts index ca35d9f7..335f69b1 100644 --- a/apps/web/src/hooks/useLayoutActions.ts +++ b/apps/web/src/hooks/useLayoutActions.ts @@ -146,6 +146,9 @@ export function useLayoutActions(): UseLayoutActionsResult { const previewDock = projectId ? (previewState.dockByProjectId[projectId] ?? null) : null; const previewSize = projectId ? (previewState.sizeByProjectId[projectId] ?? null) : null; + const previewLayoutMode = projectId + ? (previewState.layoutModeByProjectId[projectId] ?? null) + : null; const now = Date.now(); return { @@ -164,6 +167,7 @@ export function useLayoutActions(): UseLayoutActionsResult { sidebarWidths: readSidebarWidths(), previewDock, previewSize, + previewLayoutMode, }; }, [], @@ -214,6 +218,9 @@ export function useLayoutActions(): UseLayoutActionsResult { if (layout.previewSize !== null) { usePreviewStateStore.getState().setProjectSize(projectId, layout.previewSize); } + if (layout.previewLayoutMode !== null) { + usePreviewStateStore.getState().setProjectLayoutMode(projectId, layout.previewLayoutMode); + } } // ── 4. Apply terminal state ──────────────────────────────── @@ -257,6 +264,7 @@ export function useLayoutActions(): UseLayoutActionsResult { sidebarWidths: snapshot.sidebarWidths, previewDock: snapshot.previewDock, previewSize: snapshot.previewSize, + previewLayoutMode: snapshot.previewLayoutMode, }); }, [captureCurrentLayout, updateLayoutInStore], diff --git a/apps/web/src/previewStateStore.test.ts b/apps/web/src/previewStateStore.test.ts index d6da0613..ec1b73e3 100644 --- a/apps/web/src/previewStateStore.test.ts +++ b/apps/web/src/previewStateStore.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; -const STORAGE_KEY = "okcode:desktop-preview:v4"; +const STORAGE_KEY = "okcode:desktop-preview:v5"; let usePreviewStateStore: typeof import("./previewStateStore").usePreviewStateStore; let storage: Map; @@ -30,7 +30,11 @@ describe("previewStateStore", () => { dockByProjectId: {}, sizeByProjectId: {}, presetByProjectId: {}, + orientationByProjectId: {}, + customViewportByProjectId: {}, favoriteUrls: [], + layoutModeByProjectId: {}, + previousLayoutModeByProjectId: {}, }); storage.clear(); }); diff --git a/docs/releases/README.md b/docs/releases/README.md index 9b3f066d..32464fa5 100644 --- a/docs/releases/README.md +++ b/docs/releases/README.md @@ -9,6 +9,7 @@ Use this directory for versioned release notes and asset manifests only: | Version | Summary | Assets | | -------------------- | -------------------------------------------------------------------------------------------- | ----------------------------- | +| [0.18.0](v0.18.0.md) | Workspace panel, preview pop-out controls, worktree cleanup actions, and release-flow polish | [manifest](v0.18.0/assets.md) | | [0.17.0](v0.17.0.md) | Unified right-panel review, worktree cleanup controls, chat UX polish, and release hardening | [manifest](v0.17.0/assets.md) | | [0.16.1](v0.16.1.md) | Release with 8 new feature(s), 3 fix(es), 19 improvement(s) | [manifest](v0.16.1/assets.md) | | [0.16.0](v0.16.0.md) | Right-panel diff review, editable code previews, and stronger release stability | [manifest](v0.16.0/assets.md) | diff --git a/docs/releases/v0.18.0.md b/docs/releases/v0.18.0.md new file mode 100644 index 00000000..00c7c684 --- /dev/null +++ b/docs/releases/v0.18.0.md @@ -0,0 +1,67 @@ +# OK Code v0.18.0 + +**Date:** 2026-04-07 +**Tag:** [`v0.18.0`](https://github.com/OpenKnots/okcode/releases/tag/v0.18.0) + +## Summary + +Strengthen the day-to-day workspace experience with a unified right-sidebar workspace panel, more capable preview controls, broader worktree cleanup actions, and a safer release train for the CLI and web surfaces. + +## Highlights + +- **Add a dedicated workspace panel in the right sidebar.** Projects, files, and related workspace context now live in one predictable panel that is faster to scan and easier to keep open while reviewing or editing. +- **Make preview workflows more flexible.** Preview layout modes, pop-out controls, top-docked preview behavior, and tab snapshot capture make it easier to compare app states without losing orientation. +- **Expand worktree cleanup controls.** Teams can now run bulk delete-all cleanup flows and manage larger sets of disposable worktrees without repeated one-off cleanup actions. +- **Improve navigation and rendering stability.** Cached sidebar lookups and reduced preview/diff flicker keep the UI steadier under frequent thread, project, and panel updates. +- **Handle richer project files.** Binary project writes are now supported, with workspace tree behavior updated to stay readable around non-text assets. +- **Harden release operations.** The coordinated release workflow now treats CLI publication as optional when needed, and the web release path no longer emits the warnings that previously weakened ship confidence. + +## Detailed changes + +### Workspace and navigation + +- Added a unified workspace panel to the right sidebar. +- Added a project-sidebar expand-all control for faster tree browsing. +- Open diff files directly in the integrated viewer. +- Cached sidebar thread and project lookups to reduce repeated navigation work. +- Moved sidebar branding to the footer and removed the chat project badge to keep the header focused on active work. +- Cleaned up chat header panel toggles for a simpler control surface. + +### Preview and review ergonomics + +- Added preview layout modes so users can choose the right comparison layout for the task at hand. +- Added preview pop-out controls for workflows that need more room or separate windows. +- Locked browser preview positioning to the top dock for more predictable layout behavior. +- Added tab snapshot capture for preview tabs. +- Reduced preview and diff flicker during frequent workspace updates. + +### Worktrees and project files + +- Added a bulk delete-all worktree cleanup action. +- Added support for binary project writes. +- Collapsed the workspace tree more intelligently when binary-write contexts are shown so large file sets stay manageable. + +### Release reliability + +- Made CLI publishing optional in the coordinated release workflow so desktop and documentation assets can still ship when npm publication needs to be deferred. +- Fixed web release warnings to keep the release path clean before tagging. + +## Breaking changes + +- None. + +## Upgrade and install + +- **CLI:** `npm install -g okcodes@0.18.0` when the coordinated release publishes the package. +- **Desktop:** Download from [GitHub Releases](https://github.com/OpenKnots/okcode/releases/tag/v0.18.0). Filenames and attachment classes are listed in [assets.md](v0.18.0/assets.md). +- **iOS:** Available via TestFlight when the coordinated release workflow completes with signing enabled. + +## Release verification references + +- Review the [asset manifest](v0.18.0/assets.md) to confirm every expected GitHub Release attachment is present. +- Use the [rollout checklist](v0.18.0/rollout-checklist.md) for preflight, publish, and post-release verification. +- Use the [soak test plan](v0.18.0/soak-test-plan.md) if an RC is cut or if the team wants targeted verification of the new workspace and preview flows. + +## Known limitations + +OK Code remains early work in progress. Expect rough edges around session recovery, streaming edge cases, and platform-specific desktop behavior. Report issues on GitHub. diff --git a/docs/releases/v0.18.0/assets.md b/docs/releases/v0.18.0/assets.md new file mode 100644 index 00000000..00b31c51 --- /dev/null +++ b/docs/releases/v0.18.0/assets.md @@ -0,0 +1,73 @@ +# v0.18.0 — Release assets (manifest) + +Binaries are **not** stored in this git repository; they are attached to the [GitHub Release for `v0.18.0`](https://github.com/OpenKnots/okcode/releases/tag/v0.18.0) by the [Release workflow](../../.github/workflows/release.yml). + +The GitHub Release also includes **documentation attachments** with stable filenames: + +| File | Source in repo | +| --------------------------- | ------------------------------------- | +| `okcode-CHANGELOG.md` | [CHANGELOG.md](../../../CHANGELOG.md) | +| `okcode-RELEASE-NOTES.md` | [v0.18.0.md](../v0.18.0.md) | +| `okcode-ASSETS-MANIFEST.md` | This file | + +## Expected GitHub Release attachments + +The release job copies every file matching these workflow patterns into the published GitHub Release attachment set: + +| Source pattern | Attachment class | +| --------------------------- | ------------------------------------- | +| `release/*.dmg` | macOS installer images | +| `release/*.zip` | macOS updater payloads | +| `release/*.AppImage` | Linux desktop installers | +| `release/*.exe` | Windows NSIS installers | +| `release/*.blockmap` | Electron differential update metadata | +| `release/latest*.yml` | Electron updater manifests | +| `okcode-CHANGELOG.md` | Changelog attachment | +| `okcode-RELEASE-NOTES.md` | Release notes attachment | +| `okcode-ASSETS-MANIFEST.md` | Asset manifest attachment | + +If any attachment class above is missing after the workflow completes, treat the release as incomplete. + +## Desktop installers and updater payloads + +| Platform | Kind | Expected attachment class | +| ------------------- | -------------- | ------------------------- | +| macOS Apple Silicon | DMG (signed) | `*.dmg` (arm64) | +| macOS Intel | DMG (signed) | `*.dmg` (x64, compat run) | +| macOS | ZIP (updater) | `*.zip` | +| Linux x64 | AppImage | `*.AppImage` | +| Windows x64 | NSIS installer | `*.exe` | + +### macOS code signing and notarization + +All macOS DMG and ZIP payloads are expected to be code-signed with an Apple Developer ID certificate and notarized before publication. Gatekeeper should verify the signature on first launch. The hardened runtime is enabled with entitlements defined in `apps/desktop/resources/entitlements.mac.plist`. + +## Electron updater metadata + +| File pattern | Purpose | +| ------------------ | ---------------------------------------------------- | +| `latest-mac.yml` | macOS update manifest merged from release CI outputs | +| `latest-linux.yml` | Linux update manifest | +| `latest.yml` | Windows update manifest | +| `*.blockmap` | Differential download block maps | + +## iOS (TestFlight) + +The iOS build is uploaded directly to App Store Connect / TestFlight by the [Release iOS workflow](../../.github/workflows/release-ios.yml) when signing secrets are configured. No IPA artifact is attached to the GitHub Release. + +| Detail | Value | +| ----------------- | ---------------------------------- | +| Bundle ID | `com.openknots.okcode.mobile` | +| Marketing version | `0.18.0` | +| Build number | Set from `GITHUB_RUN_NUMBER` in CI | + +## Rollout documentation + +| Document | Purpose | +| -------------------------------------------- | ----------------------------------------------------------------- | +| [rollout-checklist.md](rollout-checklist.md) | Step-by-step release playbook from preflight through post-release | +| [soak-test-plan.md](soak-test-plan.md) | Structured RC soak validation for high-risk surfaces | + +## Checksums + +SHA-256 checksums are not committed here; verify downloads via GitHub's release UI or `gh release download` if you use the GitHub CLI. diff --git a/docs/releases/v0.18.0/rollout-checklist.md b/docs/releases/v0.18.0/rollout-checklist.md new file mode 100644 index 00000000..fed738a6 --- /dev/null +++ b/docs/releases/v0.18.0/rollout-checklist.md @@ -0,0 +1,64 @@ +# v0.18.0 Rollout Checklist + +Step-by-step playbook for the v0.18.0 release. Each phase must complete before advancing. + +## Phase 0: Pre-flight + +- [ ] Verify all package versions are `0.18.0`: + - `apps/server/package.json` + - `apps/desktop/package.json` + - `apps/web/package.json` + - `apps/mobile/package.json` + - `packages/contracts/package.json` +- [ ] Verify Android `versionName` and iOS `MARKETING_VERSION` both match `0.18.0`. +- [ ] Confirm `CHANGELOG.md` has `## [0.18.0] - 2026-04-07`. +- [ ] Confirm `docs/releases/v0.18.0.md` exists with Summary, Highlights, and Upgrade sections. +- [ ] Confirm `docs/releases/v0.18.0/assets.md` exists and lists every expected attachment class. +- [ ] Confirm `docs/releases/README.md` includes the v0.18.0 row. +- [ ] Run `bun run release:validate 0.18.0`. +- [ ] Confirm the working tree is clean. +- [ ] Confirm you are on `main`. + +### Quality gates + +- [ ] `bun run fmt:check` +- [ ] `bun run lint` +- [ ] `bun run typecheck` +- [ ] `bun run test` +- [ ] `bun run --cwd apps/web test:browser` +- [ ] `bun run test:desktop-smoke` +- [ ] `bun run release:smoke` + +## Phase 1: Publish + +- [ ] Push the release-prep commit to `main`. +- [ ] Create and push tag `v0.18.0`. +- [ ] Verify the coordinated `release.yml` workflow starts. +- [ ] Monitor the pipeline through Preflight, Desktop builds, optional iOS TestFlight, optional CLI publish, Publish GitHub Release, and Finalize release. + +### Asset verification + +- [ ] GitHub Release body matches `docs/releases/v0.18.0.md`. +- [ ] `okcode-CHANGELOG.md` is attached. +- [ ] `okcode-RELEASE-NOTES.md` is attached. +- [ ] `okcode-ASSETS-MANIFEST.md` is attached. +- [ ] macOS release artifacts are attached: DMG, ZIP, updater manifest, and blockmaps. +- [ ] Linux release artifacts are attached: AppImage and updater manifest if generated. +- [ ] Windows release artifacts are attached: installer, updater manifest, and blockmaps. +- [ ] If the Intel compatibility workflow is run, confirm the x64 macOS DMG is attached as well. + +## Phase 2: Post-release verification + +- [ ] `npx --yes okcodes@0.18.0 --version` returns `0.18.0`. +- [ ] macOS installer launches and passes Gatekeeper. +- [ ] Linux AppImage launches. +- [ ] Windows installer installs and launches. +- [ ] Desktop auto-update metadata is present for supported platforms. +- [ ] If iOS signing was enabled, confirm the new TestFlight build appears. +- [ ] Confirm the finalize job pushed the release version alignment commit back to `main`. + +## Phase 3: Follow-through + +- [ ] Trigger the Intel compatibility workflow if macOS x64 artifacts are required for this train. +- [ ] Update any external release references or announcements. +- [ ] Monitor reports for regressions in workspace-panel navigation, preview pop-out controls, worktree cleanup actions, and release asset publication. diff --git a/docs/releases/v0.18.0/soak-test-plan.md b/docs/releases/v0.18.0/soak-test-plan.md new file mode 100644 index 00000000..e04501c0 --- /dev/null +++ b/docs/releases/v0.18.0/soak-test-plan.md @@ -0,0 +1,38 @@ +# v0.18.0 Soak Test Plan + +Structured validation plan for the highest-risk surfaces in v0.18.0. + +## 1. Workspace panel and sidebar navigation + +| Step | Expected | Pass | +| ------------------------------------------- | -------------------------------------------------------------- | ---- | +| Open the right sidebar workspace panel | Workspace content appears without stale or missing state | [ ] | +| Expand the project tree with the new toggle | All nodes expand without layout jumps or duplicated rows | [ ] | +| Open several threads and switch projects | Cached lookups keep sidebar selection responsive | [ ] | +| Return to the sidebar after longer sessions | Branding footer stays present and header controls stay in sync | [ ] | + +## 2. Preview layouts and pop-out controls + +| Step | Expected | Pass | +| -------------------------------------------- | ---------------------------------------------------- | ---- | +| Switch between preview layout modes | Preview rearranges cleanly without stale sizing | [ ] | +| Use the preview pop-out control | Preview opens in the expected detached surface | [ ] | +| Capture a preview tab snapshot | Snapshot completes and the active tab remains stable | [ ] | +| Update content rapidly while preview is open | Preview and inline diffs do not flicker excessively | [ ] | + +## 3. Worktree cleanup and project file handling + +| Step | Expected | Pass | +| -------------------------------------- | ------------------------------------------------------------ | ---- | +| Create several temporary worktrees | All worktrees are listed accurately | [ ] | +| Trigger the delete-all cleanup action | Cleanup removes the selected worktrees without partial state | [ ] | +| Open or save a binary project file | Binary project writes succeed without corrupting tree state | [ ] | +| Reopen the project after binary writes | Workspace tree remains readable and appropriately collapsed | [ ] | + +## 4. Release regression checks + +| Step | Expected | Pass | +| ---------------------------------------------------- | -------------------------------------------- | ---- | +| Run desktop smoke coverage on the release build path | Desktop packaging remains green | [ ] | +| Verify local CLI package after publish | `okcodes --version` reports `0.18.0` | [ ] | +| Inspect GitHub Release attachments | Every class listed in `assets.md` is present | [ ] | diff --git a/packages/contracts/package.json b/packages/contracts/package.json index d8f19d34..30f593f2 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@okcode/contracts", - "version": "0.17.0", + "version": "0.18.0", "private": true, "files": [ "dist" From 719c5d3ba9baf6b20fdb8ae6095f58f3fb1441cf Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Tue, 7 Apr 2026 06:16:40 -0500 Subject: [PATCH 2/3] Wrap OpenClaw teardown and health probe failures - Normalize teardown errors into provider adapter errors - Tag OpenClaw health probe failures for clearer diagnostics --- .../src/provider/Layers/OpenClawAdapter.ts | 16 ++++++++++++++-- .../src/provider/Layers/ProviderHealth.ts | 19 +++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/apps/server/src/provider/Layers/OpenClawAdapter.ts b/apps/server/src/provider/Layers/OpenClawAdapter.ts index 3172916b..f78c7b5c 100644 --- a/apps/server/src/provider/Layers/OpenClawAdapter.ts +++ b/apps/server/src/provider/Layers/OpenClawAdapter.ts @@ -796,14 +796,26 @@ function makeOpenClawAdapter(options?: OpenClawAdapterLiveOptions) { id: context.nextRpcId++, }), ), - catch: (e) => e as Error, + catch: (cause) => + new ProviderAdapterProcessError({ + provider: PROVIDER, + threadId: context.threadId, + detail: "Failed to send session.stop during teardown.", + cause, + }), }).pipe(Effect.orElseSucceed(() => undefined)); } // Close WebSocket. yield* Effect.try({ try: () => context.ws.close(), - catch: (e) => e as Error, + catch: (cause) => + new ProviderAdapterProcessError({ + provider: PROVIDER, + threadId: context.threadId, + detail: "Failed to close websocket during teardown.", + cause, + }), }).pipe(Effect.orElseSucceed(() => undefined)); // Interrupt stream fiber if active. diff --git a/apps/server/src/provider/Layers/ProviderHealth.ts b/apps/server/src/provider/Layers/ProviderHealth.ts index 6c3b49f5..2b24becb 100644 --- a/apps/server/src/provider/Layers/ProviderHealth.ts +++ b/apps/server/src/provider/Layers/ProviderHealth.ts @@ -14,7 +14,18 @@ import type { ServerProviderStatus, ServerProviderStatusState, } from "@okcode/contracts"; -import { Array, Effect, Fiber, FileSystem, Layer, Option, Path, Result, Stream } from "effect"; +import { + Array, + Data, + Effect, + Fiber, + FileSystem, + Layer, + Option, + Path, + Result, + Stream, +} from "effect"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { @@ -28,6 +39,10 @@ const DEFAULT_TIMEOUT_MS = 4_000; const CODEX_PROVIDER = "codex" as const; const CLAUDE_AGENT_PROVIDER = "claudeAgent" as const; +class OpenClawHealthProbeError extends Data.TaggedError("OpenClawHealthProbeError")<{ + cause: unknown; +}> {} + // ── Pure helpers ──────────────────────────────────────────────────── export interface CommandResult { @@ -630,7 +645,7 @@ const checkOpenClawProviderStatus: Effect.Effect cause as Error, + catch: (cause) => new OpenClawHealthProbeError({ cause }), }).pipe(Effect.result); if (Result.isFailure(probeResult)) { From adb5441fc8439f84e56c6828c1904b727b93bce2 Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Tue, 7 Apr 2026 06:17:26 -0500 Subject: [PATCH 3/3] Use session thread ID during OpenClaw teardown - Report teardown errors against `context.session.threadId` - Keep websocket and session.stop cleanup aligned with the active session --- apps/server/src/provider/Layers/OpenClawAdapter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/server/src/provider/Layers/OpenClawAdapter.ts b/apps/server/src/provider/Layers/OpenClawAdapter.ts index f78c7b5c..4bda212e 100644 --- a/apps/server/src/provider/Layers/OpenClawAdapter.ts +++ b/apps/server/src/provider/Layers/OpenClawAdapter.ts @@ -799,7 +799,7 @@ function makeOpenClawAdapter(options?: OpenClawAdapterLiveOptions) { catch: (cause) => new ProviderAdapterProcessError({ provider: PROVIDER, - threadId: context.threadId, + threadId: context.session.threadId, detail: "Failed to send session.stop during teardown.", cause, }), @@ -812,7 +812,7 @@ function makeOpenClawAdapter(options?: OpenClawAdapterLiveOptions) { catch: (cause) => new ProviderAdapterProcessError({ provider: PROVIDER, - threadId: context.threadId, + threadId: context.session.threadId, detail: "Failed to close websocket during teardown.", cause, }),