diff --git a/README.md b/README.md index 24dd1679..d6e6fae9 100644 --- a/README.md +++ b/README.md @@ -1,163 +1,225 @@ # OK Code -A minimal web GUI for coding agents. Currently supports Codex and Claude, with more providers coming. - -## Quick Start - -```bash -npx okcodes +## 1) What this project is + +OK Code is a desktop-first orchestration platform for interactive coding agents. +It connects a local runtime (`apps/server`) that manages provider sessions with a +React client (`apps/web`) that renders live orchestration events and session state. + +## 2) Architecture and core flow + +### High-level flow + +```mermaid +flowchart LR + User["Developer"] --> UI["apps/web (React/Vite)"] + UI --> WS["apps/server/src/wsServer.ts
NativeApi + WS routing"] + WS --> PM["apps/server/src/providerManager.ts
Session request orchestration"] + PM --> SM["apps/server/src/codexAppServerManager.ts
Provider process lifecycle"] + SM --> Codex["codex app-server (process)"] + PM --> Contracts["@okcode/contracts"] + WS --> Contracts + Shared["packages/shared/*"] --> PM + Shared --> UI ``` -This starts the OK Code server and opens your browser. The app automatically detects which providers you have installed. +### Component responsibilities + +- `apps/server` + - Owns session lifecycle: start/resume, reconnect handling, provider multiplexing. + - Provides the WebSocket API that the web app talks to. + - Converts provider output into shared orchestration-domain events. +- `apps/web` + - Owns user interaction, streaming UI, logs, and local state. + - Consumes server events and sends control actions back through `NativeApi`. +- `packages/contracts` + - Shared protocol/event types (`effect/Schema`) used by both sides. + - Keep this package schema-focused and stable. +- `packages/shared` + - Cross-package runtime helpers with explicit subpath exports (for example `@okcode/shared/git`). + - Use shared helpers instead of duplicating transport/session utility code. + +## 3) Repository map + +```text +apps/ + server/ Runtime orchestration and websocket gateway. + web/ React conversation and orchestration UI. + desktop/ Electron shell and client bootstrap. + marketing/ Marketing/public site. + mobile/ Capacitor app wrapper/build tooling. +packages/ + contracts/ Shared protocol/schema definitions. + shared/ Shared runtime utilities. +docs/ + releases/ Release process, release notes, and asset manifests. +scripts/ + Build/release/bootstrap scripts and local tooling. +``` -Or install the [desktop app from the Releases page](https://github.com/OpenKnots/okcode/releases). +## 4) Setup and local development -### Provider Setup +### Required tooling -OK Code supports multiple AI providers. You need **at least one** configured to start coding. +- Bun (matching project engine): `bun@1.3.11+` +- Node 24+ (repo declares `bun` + `node` engine compatibility) +- Xcode for iOS-related work +- macOS or Linux for development -
-Option A: OpenAI (Codex CLI) +### Install -1. Install: `npm install -g @openai/codex` -2. Authenticate: `codex login` -3. Verify: `codex login status` +```bash +bun install +``` -If using a custom model provider (Azure OpenAI, Portkey, etc.), configure `model_provider` in `~/.codex/config.toml` instead — no `codex login` needed. +### Run everything -
+```bash +bun dev +``` -
-Option B: Anthropic (Claude Code) +### Common workflows -1. Install: `npm install -g @anthropic-ai/claude-code` -2. Authenticate: `claude auth login` -3. Verify: `claude auth status` +```bash +bun dev:server # apps/server only +bun dev:web # apps/web only +bun dev:desktop # desktop shell + electron dev flow +bun dev:marketing # marketing site +``` -
+Build marketing directly: -> [!TIP] -> You can install both providers and switch between them per session. +```bash +bun install +bun run build:marketing +``` -### Troubleshooting +If `bun run build:marketing` fails with `next: command not found`, run `bun install` first to restore workspace dependencies. -Run the built-in diagnostic to check your setup: +### Package scoped examples ```bash -npx okcodes doctor +bun --filter @okcode/web dev +bun --filter okcodes dev +bun --filter @okcode/desktop build +bun --filter @okcode/contracts typecheck ``` -If OK Code shows a provider error banner after launch: +> `bun --filter okcodes dev` is useful when you explicitly want the server package by +> its legacy package name (`okcodes`). -| Banner message | Fix | -| ------------------------------ | ------------------------------------------------- | -| "not installed or not on PATH" | Install the CLI (see above), then restart OK Code | -| "not authenticated" | Run the login command for that provider | -| "version check failed" | Update the CLI to the latest version | +### Quick-start operator matrix -> [!NOTE] -> OK Code launches even without providers configured — you can explore the UI and configure provider binary paths from **Settings** before starting a session. +| Intent | Command | Expected result | Where to verify | +| ------------------------- | -------------------------------- | ------------------------------------------------ | ------------------------------------------------------ | +| Boot full local stack | `bun dev` | workspace starts dev scripts and shared services | logs show dev server/process startup | +| Run desktop app flow only | `bun dev:desktop` | local Electron + web bundle pipeline starts | verify desktop window opens and server socket connects | +| Build desktop artifacts | `bun build:desktop` | production desktop build output generated | workspace build pipeline succeeds in logs | +| Build web app | `bun --filter @okcode/web build` | Vite build output produced | check dist artifacts and exit code 0 | +| Validate formatting | `bun run fmt:check` | no formatting diffs | command exits 0 | +| Validate lint rules | `bun run lint` | lint passes | no lints in output | +| Validate TypeScript | `bun run typecheck` | no compile errors | all package typechecks pass | +| Run tests | `bun run test` | Vitest suites execute | exit code 0, no failing tests | -## Development Setup +## 5) Runtime behavior (what happens during a session) -**Prerequisites**: [Bun](https://bun.sh) >= 1.3.9, [Node.js](https://nodejs.org) >= 24.13.1 +1. Web UI opens WebSocket and subscribes to orchestration events. +2. UI submits user action to a `NativeApi` endpoint in `wsServer.ts`. +3. Server dispatches the request through `providerManager`. +4. `codexAppServerManager` starts or resumes the underlying `codex app-server` process. +5. Process outputs are parsed, normalized into shared events, and pushed back to UI. +6. UI applies deterministic reducers for rendering logs, state, and action controls. -```bash -bun install -bun dev # start server + web in parallel -``` +Reconnect and recovery are first-class behaviors: -This runs the contracts build, then starts the server (port 3773) and web app (port 5733) together via Turbo. +- active process restarts should resume context when session state is available, +- failed parses are surfaced as explicit error states instead of silent fallback behavior. -Other dev commands: +## 6) Contracts and compatibility -```bash -bun dev:server # server only -bun dev:web # web only -bun dev:desktop # Electron desktop + web -bun dev:marketing # Astro marketing site -``` +- All contract changes should begin in `packages/contracts`. +- Update both producer (`apps/server`) and consumer (`apps/web`) in the same commit. +- Validate generated type surface with: + - `bun run typecheck` + - targeted package `typecheck` scripts when needed. -Build marketing directly: +## 7) Build and quality gates ```bash -bun install -bun run build:marketing +bun run fmt +bun run fmt:check +bun run lint +bun run typecheck +bun run test ``` -If `bun run build:marketing` fails with `next: command not found`, run `bun install` first to restore workspace dependencies. +Notes: -Quality checks: +- `bun run test` is the required test entrypoint. +- Formatting uses `oxfmt`; lint uses `oxlint`. +- Release tasks in CI are intentionally strict and require clean checks to proceed. -```bash -bun fmt # format (oxfmt) -bun lint # lint (oxlint) -bun typecheck # type-check all packages -bun run test # run tests (Vitest) -``` +## 8) Release operations -## Architecture +Release is mostly driven by `.github/workflows/release.yml` and docs in `docs/releases`. -OK Code is a monorepo with four apps and two shared packages, orchestrated by [Turbo](https://turbo.build). +- Trigger on tag push (`vX.Y.Z`) or `workflow_dispatch`. +- Preflight does `bun run lint`, `bun run typecheck`, `bun run test`, + and release smoke. +- Artifact build step executes `bun run dist:desktop:artifact`. +- Publishing requires release notes + asset manifest for the version. -``` -┌─────────────────────────────────┐ -│ Browser (React + Vite) │ -│ wsTransport (state machine) │ -│ Typed push decode at boundary │ -└──────────┬──────────────────────┘ - │ ws://localhost:3773 -┌──────────▼──────────────────────┐ -│ apps/server (Node.js) │ -│ WebSocket + HTTP static server │ -│ OrchestrationEngine │ -│ ProviderService │ -└──────────┬──────────────────────┘ - │ JSON-RPC over stdio -┌──────────▼──────────────────────┐ -│ codex app-server │ -└─────────────────────────────────┘ -``` +For a practical walkthrough, use the release playbook in +[`docs/releases/README.md`](/Users/buns/.okcode/worktrees/okcode/okcode-ddc899c0/docs/releases/README.md). + +## 9) Extending the system (recommended pattern) -The server spawns `codex app-server` as a child process, communicating over JSON-RPC on stdio. Provider runtime events are normalized into orchestration domain events and pushed to the browser over WebSocket. +1. Start at the boundary where behavior changes: + - event shape → `packages/contracts` + - runtime orchestration → `apps/server` + - rendering/state handling → `apps/web` +2. Add/update shared types first. +3. Update server-side translation/projection next. +4. Update UI consumers and add tests or focused regression checks. +5. Run all quality gates before commit. -### Packages +## 10) Security and operational posture -| Package | Path | Role | -| ------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `okcodes` | `apps/server` | Node.js CLI and WebSocket server. Wraps Codex app-server, serves the React web app, and manages provider sessions. | -| `@okcode/web` | `apps/web` | React 19 + Vite SPA. Session UX, conversation rendering, and client-side state via Zustand. Connects to the server over WebSocket. | -| `@okcode/desktop` | `apps/desktop` | Electron shell that bundles the server and web app into a native desktop application with auto-updates. | -| `@okcode/marketing` | `apps/marketing` | Astro marketing site. | -| `@okcode/contracts` | `packages/contracts` | Shared [Effect](https://effect.website) schemas and TypeScript contracts for the WebSocket protocol, provider events, orchestration model, and session types. Schema-only — no runtime logic. | -| `@okcode/shared` | `packages/shared` | Shared runtime utilities (git, logging, shell, networking). Uses explicit subpath exports (`@okcode/shared/git`, etc.) — no barrel index. | +- No secrets in files tracked by git. +- Validate untrusted inputs before invoking spawn/process APIs. +- Keep cleanup idempotent (process kill, temp files, sockets). +- Emit explicit telemetry for failure modes to avoid hidden partial failures. -### Key Technologies +## 11) FAQ -- **Runtime**: Node.js + Bun -- **UI**: React 19, Vite 8, Tailwind CSS 4, TanStack Router & Query -- **Server**: Effect, WebSocket (`ws`), node-pty -- **Desktop**: Electron -- **Schemas**: Effect Schema (in `@okcode/contracts`) -- **Build**: Turbo, tsdown -- **Lint/Format**: oxlint, oxfmt -- **Tests**: Vitest, Playwright +- **Why are x64 macOS artifacts sometimes absent from release matrix?** + The release workflow can be configured to build only Apple Silicon by default for `workflow_dispatch` with `mac_arm64_only=true`. +- **Why strict release gates?** + They prevent format/type drift from reaching published artifacts. +- **Can I run release checks locally first?** + Yes, run the section 7 checks and create your release notes manifest files before creating a tag. -### Event Flow +## 12) Operational checklists -1. The browser opens a WebSocket to the server and registers typed listeners. -2. User actions become typed requests sent through the WebSocket transport. -3. The server routes requests to `ProviderService`, which talks to `codex app-server` over JSON-RPC. -4. Provider events are ingested, normalized into orchestration events, and persisted. -5. The server pushes domain events back to the browser through an ordered push bus (`orchestration.domainEvent` channel). -6. Async work (checkpoints, command reactions) runs through queue-backed workers and emits typed receipts on completion. +### Pre-PR gate -For the full architecture with sequence diagrams, see [.docs/architecture.md](.docs/architecture.md). +1. `bun run fmt:check` +2. `bun run lint` +3. `bun run typecheck` +4. `bun run test` +5. targeted package checks if changing app/domain boundaries -## Contributing +### Pre-release hardening -Read [CONTRIBUTING.md](./CONTRIBUTING.md) before opening an issue or PR. +1. Ensure release notes and asset manifest are prepared. +2. Confirm release matrix intent (`mac_arm64_only` expectation). +3. Confirm signing secrets availability for macOS/Windows targets. +4. Confirm `docs/releases/v.md` and `docs/releases/v/assets.md` exist. +5. Trigger release and monitor all jobs. -## Support +## 12) Contributing expectations -Join the [Discord](https://discord.gg/openknot). +- Favor small scoped changes with clear ownership. +- Keep protocol surfaces version-consistent and documented. +- Update docs whenever startup flow, payload contracts, or release behavior changes. +- Prefer correctness over convenience in stream/retry code. diff --git a/apps/marketing/app/globals.css b/apps/marketing/app/globals.css index e1f14def..2a2a5348 100644 --- a/apps/marketing/app/globals.css +++ b/apps/marketing/app/globals.css @@ -78,10 +78,10 @@ } @theme inline { - --font-sans: var(--font-dm-sans), "DM Sans", -apple-system, BlinkMacSystemFont, - "Segoe UI", system-ui, sans-serif; - --font-mono: "SF Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, - monospace; + --font-sans: + var(--font-dm-sans), "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, + sans-serif; + --font-mono: "SF Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); diff --git a/apps/marketing/components/ai-section.tsx b/apps/marketing/components/ai-section.tsx index 9bd2708e..0c14deab 100644 --- a/apps/marketing/components/ai-section.tsx +++ b/apps/marketing/components/ai-section.tsx @@ -310,7 +310,7 @@ export function AISection() { {/* MCP Code Snippet - Use semantic tokens for code syntax */}
-

//mcp.sprint.app/sse

+

{"//mcp.sprint.app/sse"}

"mcpServers" diff --git a/apps/marketing/components/dashboard/dashboard-inbox.tsx b/apps/marketing/components/dashboard/dashboard-inbox.tsx index 6365b52a..5c6e8e38 100644 --- a/apps/marketing/components/dashboard/dashboard-inbox.tsx +++ b/apps/marketing/components/dashboard/dashboard-inbox.tsx @@ -184,7 +184,7 @@ function IssueRow({ issue, selected, onClick, - isMobile, + isMobile: _isMobile, }: { issue: Issue; selected: boolean; diff --git a/apps/marketing/components/dashboard/dashboard-issue-detail.tsx b/apps/marketing/components/dashboard/dashboard-issue-detail.tsx index dc650742..81e02b51 100644 --- a/apps/marketing/components/dashboard/dashboard-issue-detail.tsx +++ b/apps/marketing/components/dashboard/dashboard-issue-detail.tsx @@ -484,9 +484,9 @@ export function DashboardIssueDetail({

- {issue.activity.map((activity, index) => ( + {issue.activity.map((activity) => ( {/* Logo grid with variety */}
- {companies.map((company, i) => { + {companies.map((company, companyIndex) => { const Icon = company.icon; return ( diff --git a/apps/marketing/components/product-direction-section.tsx b/apps/marketing/components/product-direction-section.tsx index e6bdc0f4..8b1b462c 100644 --- a/apps/marketing/components/product-direction-section.tsx +++ b/apps/marketing/components/product-direction-section.tsx @@ -77,13 +77,13 @@ export function ProductDirectionSection() {
{/* Tick marks row */}
- {Array.from({ length: 60 }).map((_, i) => ( + {Array.from({ length: 60 }, (_, tickIndex) => tickIndex).map((tickIndex) => (
))} diff --git a/apps/marketing/components/ui-panel-inbox.tsx b/apps/marketing/components/ui-panel-inbox.tsx index 299263bc..d345c3f1 100644 --- a/apps/marketing/components/ui-panel-inbox.tsx +++ b/apps/marketing/components/ui-panel-inbox.tsx @@ -87,9 +87,9 @@ export function UIPanelInbox() { {/* List */}
- {items.map((item, index) => ( + {items.map((item) => (
diff --git a/apps/marketing/components/ui/carousel.tsx b/apps/marketing/components/ui/carousel.tsx index c43b8262..cd7c9bec 100644 --- a/apps/marketing/components/ui/carousel.tsx +++ b/apps/marketing/components/ui/carousel.tsx @@ -102,19 +102,22 @@ function Carousel({ }; }, [api, onSelect]); + const contextValue = React.useMemo( + () => ({ + carouselRef, + api: api, + opts, + orientation: orientation || (opts?.axis === "y" ? "vertical" : "horizontal"), + scrollPrev, + scrollNext, + canScrollPrev, + canScrollNext, + }), + [api, canScrollNext, canScrollPrev, carouselRef, orientation, opts, scrollNext, scrollPrev], + ); + return ( - +
({ config }), [config]); return ( - +
- {errors.map((error, index) => error?.message &&
  • {error.message}
  • )} + {errors + .filter((error): error is { message: string } => typeof error?.message === "string") + .map((error) => ( +
  • {error.message}
  • + ))} ); }, [children, errors]); diff --git a/apps/marketing/components/ui/form.tsx b/apps/marketing/components/ui/form.tsx index c2c8525d..4c908e57 100644 --- a/apps/marketing/components/ui/form.tsx +++ b/apps/marketing/components/ui/form.tsx @@ -33,8 +33,10 @@ const FormField = < >({ ...props }: ControllerProps) => { + const fieldContextValue = React.useMemo(() => ({ name: props.name }), [props.name]); + return ( - + ); @@ -71,9 +73,10 @@ const FormItemContext = React.createContext({} as FormItem function FormItem({ className, ...props }: React.ComponentProps<"div">) { const id = React.useId(); + const itemContextValue = React.useMemo(() => ({ id }), [id]); return ( - +
    ); diff --git a/apps/marketing/components/ui/toggle-group.tsx b/apps/marketing/components/ui/toggle-group.tsx index c93b7ae6..22476099 100644 --- a/apps/marketing/components/ui/toggle-group.tsx +++ b/apps/marketing/components/ui/toggle-group.tsx @@ -19,6 +19,7 @@ function ToggleGroup({ children, ...props }: React.ComponentProps & VariantProps) { + const contextValue = React.useMemo(() => ({ variant, size }), [size, variant]); return ( - - {children} - + {children} ); } diff --git a/apps/mobile/ios/App/CapApp-SPM/README.md b/apps/mobile/ios/App/CapApp-SPM/README.md index 03964db9..24f5e2ff 100644 --- a/apps/mobile/ios/App/CapApp-SPM/README.md +++ b/apps/mobile/ios/App/CapApp-SPM/README.md @@ -1,5 +1,88 @@ -# CapApp-SPM +# CapApp-SPM (Capacitor Swift Package) -This package is used to host SPM dependencies for your Capacitor project +This directory is a generated Swift Package integration point for the native mobile +shell. It is managed by Capacitor tooling and should be treated as a reproducible +native dependency boundary. -Do not modify the contents of it or there may be unintended consequences. +## 1) What this package is + +- Package name: `CapApp-SPM` +- Product: `CapApp-SPM` +- Target(s): `CapApp-SPM` +- Platforms: iOS 15+ + +Primary goals: + +- provide a stable dependency entrypoint into the native layer, +- keep native package metadata separate from app JS source trees, +- avoid hand-editing generated package internals. + +## 2) Files here + +- `Package.swift` +- `Sources/` (package targets/source) + +The current `Package.swift` includes local package links into Bun-managed `node_modules` +paths, which is why it is intentionally generated rather than hand-maintained. + +## 3) Install and wire into an Xcode project + +1. In Xcode, open your `.xcworkspace` / `.xcodeproj`. +2. Go to **File → Add Packages…**. +3. Add by local path: + - `apps/mobile/ios/App/CapApp-SPM` +4. Add the `CapApp-SPM` product to the appropriate app target. + +### Target mapping quick reference + +- Package: `CapApp-SPM` +- Product: `CapApp-SPM` +- Usually added to the main app target and the extensions that depend on Capacitor modules. + +## 4) Regenerating after dependency changes + +When web/native dependency versions change in `apps/mobile`, regenerate this package +using your normal Capacitor sync workflow (outside this folder) and commit generated +updates if required. + +Recommended sequence: + +```bash +cd apps/mobile +bun run sync +cd ios/App +# regenerate native project references through your normal mobile tooling +``` + +Then reopen Xcode and run package resolve. + +## 5) Build and debug checks + +### Before a build + +- Open the workspace cleanly. +- Remove stale derived data if dependency resolution changed. +- Validate package resolve on local machine first. + +### Common failure states + +- **Package not found / cannot resolve product** + - verify path points to `CapApp-SPM/` and Xcode selected the correct package product. +- **Build fails with module resolution issues** + - clean build folder and re-resolve package dependencies. +- **Code signing breaks after package edits** + - re-check provisioning profile and signing identity for native targets. + +## 6) CI/release implications + +For release and package integrity: + +- keep native package updates deterministic, +- avoid ad-hoc local path edits that are not reflected in source control, +- verify any package-manager-driven changes are generated and committed with the + corresponding release or mobile update. + +## 7) Reference + +This package is separate from release docs in [`docs/releases/README.md`](/Users/buns/.okcode/worktrees/okcode/okcode-ddc899c0/docs/releases/README.md), +which controls desktop artifact publication and GitHub release behavior. diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx index 70a23e26..98deb819 100644 --- a/apps/web/src/components/DiffPanel.tsx +++ b/apps/web/src/components/DiffPanel.tsx @@ -418,9 +418,11 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { () => renderableFiles.map((fileDiff) => resolveFileDiffPath(fileDiff)), [renderableFiles], ); - const activeReviewState = patchReviewSelectionKey - ? (reviewStateBySelectionKey[patchReviewSelectionKey] ?? {}) - : {}; + const activeReviewState = useMemo(() => { + return patchReviewSelectionKey + ? (reviewStateBySelectionKey[patchReviewSelectionKey] ?? {}) + : {}; + }, [patchReviewSelectionKey, reviewStateBySelectionKey]); const acceptedFileCount = useMemo( () => renderableFilePaths.reduce( diff --git a/apps/web/src/components/pr-review/RawPatchViewer.tsx b/apps/web/src/components/pr-review/RawPatchViewer.tsx index 7d6d1be8..c5aad318 100644 --- a/apps/web/src/components/pr-review/RawPatchViewer.tsx +++ b/apps/web/src/components/pr-review/RawPatchViewer.tsx @@ -446,14 +446,27 @@ export function RawPatchViewer({ text, reason }: { text: string; reason: string ) : ( /* Fallback: no file structure detected, render all lines with highlighting */
    - {lines.map((line, index) => ( - - ))} + {lines + .reduce>( + (items, line, lineIndex) => { + const lineNumber = lineIndex + 1; + items.push({ + line, + lineNumber, + key: `${lineNumber}-${line}`, + }); + return items; + }, + [], + ) + .map(({ key, line, lineNumber }) => ( + + ))}
    )}
    diff --git a/apps/web/src/store.ts b/apps/web/src/store.ts index ea105142..06f3b327 100644 --- a/apps/web/src/store.ts +++ b/apps/web/src/store.ts @@ -260,26 +260,28 @@ export function syncServerReadModel(state: AppState, readModel: OrchestrationRea } : null, messages: thread.messages.map((message) => { - const attachments = message.attachments?.map((attachment) => ({ - ...(attachment.type === "image" - ? { - type: "image" as const, - id: attachment.id, - name: attachment.name, - mimeType: attachment.mimeType, - sizeBytes: attachment.sizeBytes, - url: toAttachmentPreviewUrl(attachmentPreviewRoutePath(attachment.id)), - previewUrl: toAttachmentPreviewUrl(attachmentPreviewRoutePath(attachment.id)), - } - : { - type: "file" as const, - id: attachment.id, - name: attachment.name, - mimeType: attachment.mimeType, - sizeBytes: attachment.sizeBytes, - url: toAttachmentPreviewUrl(attachmentPreviewRoutePath(attachment.id)), - }), - })); + const attachments = message.attachments?.map((attachment) => { + const baseAttachment = { + id: attachment.id, + name: attachment.name, + mimeType: attachment.mimeType, + sizeBytes: attachment.sizeBytes, + url: toAttachmentPreviewUrl(attachmentPreviewRoutePath(attachment.id)), + }; + + if (attachment.type === "image") { + return { + type: "image" as const, + previewUrl: toAttachmentPreviewUrl(attachmentPreviewRoutePath(attachment.id)), + ...baseAttachment, + }; + } + + return { + type: "file" as const, + ...baseAttachment, + }; + }); const normalizedMessage: ChatMessage = { id: message.id, role: message.role, diff --git a/docs/releases/README.md b/docs/releases/README.md index 5d00dd6f..d01fafe2 100644 --- a/docs/releases/README.md +++ b/docs/releases/README.md @@ -1,25 +1,180 @@ -# Release notes - -Human-readable notes and asset inventories for tagged releases. - -| Version | Notes | Assets | -| -------------------- | ------------------------------------------------------------ | ----------------------------- | -| [0.0.12](v0.0.12.md) | Release 0.0.12 with latest preflight fixes and Apple Silicon | [manifest](v0.0.12/assets.md) | -| [0.0.12](v0.0.12.md) | Release 0.0.12 with latest preflight fixes and Apple Silicon | [manifest](v0.0.12/assets.md) | -| [0.0.11](v0.0.11.md) | Release 0.0.11 with Apple Silicon-only macOS default asset m | [manifest](v0.0.11/assets.md) | -| [0.0.11](v0.0.11.md) | Release 0.0.11 with Apple Silicon-only macOS default asset m | [manifest](v0.0.11/assets.md) | -| [0.0.11](v0.0.11.md) | Release 0.0.11 with Apple Silicon-only macOS default asset m | [manifest](v0.0.11/assets.md) | -| [0.0.10](v0.0.10.md) | Release 0.0.10 with desktop packaging matrix updated to Appl | [manifest](v0.0.10/assets.md) | -| [0.0.9](v0.0.9.md) | Review fixes, skill dialog polish, and marketing refresh | [manifest](v0.0.9/assets.md) | -| [0.0.8](v0.0.8.md) | Skills, localization, attachment flow, and UI hardening | [manifest](v0.0.8/assets.md) | -| [0.0.7](v0.0.7.md) | Navigation, clone flow, plan checklists, preview presets | [manifest](v0.0.7/assets.md) | -| [0.0.6](v0.0.6.md) | PR + preview polish, env persistence, search, landing page | [manifest](v0.0.6/assets.md) | -| [0.0.5](v0.0.5.md) | Git workflows, PR review, mobile shell | [manifest](v0.0.5/assets.md) | -| [0.0.4](v0.0.4.md) | PR review, desktop preview, release tooling | [manifest](v0.0.4/assets.md) | -| [0.0.3](v0.0.3.md) | Onboarding, code viewer, PRs | [manifest](v0.0.3/assets.md) | -| [0.0.2](v0.0.2.md) | Patch release | [manifest](v0.0.2/assets.md) | -| [0.0.1](v0.0.1.md) | First public tag | [manifest](v0.0.1/assets.md) | - -The repository root [CHANGELOG.md](../../CHANGELOG.md) summarizes versions in Keep a Changelog form. - -Each release workflow also uploads **`okcode-CHANGELOG.md`**, **`okcode-RELEASE-NOTES.md`**, and **`okcode-ASSETS-MANIFEST.md`** to the GitHub Release (see [.github/workflows/release.yml](../../.github/workflows/release.yml)). +# Release Playbook (Desktop + site assets) + +This document defines how to produce, validate, and publish official releases from +`main` with the current GitHub Actions pipeline. + +## 1) Trigger modes + +### 1.1 Tag-driven release + +- Push a tag in the form `v` (for example `v0.0.10`). +- `preflight` resolves `version` from the tag and runs required checks. + +### 1.2 Manual dispatch + +- Uses `workflow_dispatch` inputs: + - `version` (required; examples: `0.0.10` or `0.0.10-rc.1`) + - `mac_arm64_only` (default: `true`) +- Useful for controlled smoke/release dry-runs. + +## 2) Current CI stages + +### 2.1 `configure` + +- Selects release matrix. +- macOS default is Apple Silicon only when `mac_arm64_only=true`: + - `macos-14` + `mac` + `dmg` + `arm64` +- Full matrix (default for manual off switch) includes: + - `macos-14` + `arm64` + - `ubuntu-24.04` + `x64` AppImage + - `windows-2022` + `x64` NSIS + +### 2.2 `preflight` + +Executed on Ubuntu and includes: + +- dependency install (`bun install --frozen-lockfile`) +- `bun run lint` +- `bun run typecheck` +- `bun run test` +- `bun run release:smoke` + +Any failure here blocks build and publish jobs. + +### 2.3 `build` (matrixed) + +For each target: + +1. Checkout release ref. +2. Install dependencies. +3. Align package versions (`scripts/update-release-package-versions.ts`). +4. Build desktop artifact with `bun run dist:desktop:artifact`. +5. Collect release outputs (`release/*.dmg`, `release/*.AppImage`, `release/*.exe`, blockmaps, `latest*.yml`). +6. Upload per-platform artifact bundle. + +### 2.4 `release` + +- downloads all uploaded build artifacts, +- requires release docs to exist: + - `docs/releases/v.md` + - `docs/releases/v/assets.md` +- stages these as release body artifacts, then publishes release via `softprops/action-gh-release`. + +### 2.5 `finalize` + +- validates `RELEASE_APP_ID` / `RELEASE_APP_PRIVATE_KEY`, +- creates GitHub App token and bot identity, +- re-runs version bump flow and updates lockfile if needed, +- pushes version updates back to `main`. + +## 2.6 Operational matrix (release commands) + +| Trigger | Command/target | Expected output | Failure signal | Recovery | +| -------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------- | ---------------------------------------------------------- | +| Tag-driven release | push `v` | `configure` → `preflight` → matrix `build` → `release` → `finalize` all succeed | missing/failed preflight gate | fix local violations, update commit, re-tag if needed | +| Manual release | workflow_dispatch with `version` | same stages as tag-driven | matrix wrong for architecture targets | set `mac_arm64_only` explicitly | +| Format regression | `bun run fmt` | files rewritten | `bun run fmt:check` fails in `preflight` | commit formatter output | +| Type/contract regressions | `bun run typecheck` | clean compile output | `TS` errors in preflight | update contracts and both producer/consumer sides together | +| Release content regression | `git show` on `docs/releases/v.md` + `docs/releases/v/assets.md` | both files present and committed | `release` job fails missing files | add both files and rerun release | +| Asset mismatch | build logs / upload-artifact | expected platform assets in `release-publish` | `if-no-files-found: error` or missing files | verify `release/` output names and rerun build | + +## 3) Required release assets + +For each release version `X.Y.Z`, commit: + +- `docs/releases/vX.Y.Z.md` (release notes body) +- `docs/releases/vX.Y.Z/assets.md` (asset manifest used in release publish step) + +The publish step copies both into `release-assets` and uses them for release artifacts. + +## 4) Standard release checklist + +1. Start from `main`: + + ```bash + git checkout main + git pull --ff-only + ``` + +2. Ensure current format/lint/type/test gates are green locally: + + ```bash + bun run fmt + bun run fmt:check + bun run lint + bun run typecheck + bun run test + ``` + +3. Prepare release notes and asset manifest: + + ```bash + mkdir -p docs/releases/v0.0.10 + touch docs/releases/v0.0.10.md + touch docs/releases/v0.0.10/assets.md + ``` + +4. Commit release notes updates. + +5. Cut and push tag: + + ```bash + git tag -a v0.0.10 -m "Release 0.0.10" + git push origin v0.0.10 + ``` + +6. Watch workflow progress: + - `Release Desktop / preflight` + - `Release Desktop / build (...)` + - `Release Desktop / release` + - `Release Desktop / finalize` + +7. Verify final output includes: + - expected platform artifacts, + - release body and assets manifest, + - optional signing/ notarization status, + - version bumps committed by finalize job (if any). + +## 5) Failure triage + +### 5.1 Preflight failures + +- **Lint/typecheck/test failure** + - reproduce locally with the same command from section 4. + - fix at source, rerun gate, then re-run tag/release path. + +- **Release smoke failure** + - inspect `bun run release:smoke` logs and confirm expected runtime prerequisites. + +### 5.2 Build failures + +- **Wrong architecture in matrix** + - confirm intended architecture matrix by checking `configure` job output. + - for Apple Silicon-only runs, ensure `mac_arm64_only` is true. +- **Signing/notarization failure** + - confirm required GitHub secrets are present in repository settings. +- **No files found during upload** + - check artifact output paths (`release/*.dmg`, `release/*.AppImage`, etc.) + - verify build command produced files in `release/`. + +### 5.3 Release staging failures + +- **Missing `docs/releases/vX.md` or `docs/releases/vX/assets.md`** + - create both files before triggering release. +- **`softprops` failures (`fail_on_unmatched_files`)** + - ensure all build artifacts + docs copies exist in `release-assets`. + +## 6) Release policy and best practices + +- Prefer single-purpose PRs for release content vs release infra changes. +- Keep release notes and manifest content deterministic for reproducibility. +- Prefer appending bugfixes/notes to the current patch version before bumping major/minor. +- Keep signing certificates and identity changes centralized in CI secrets. +- Use the changelog + release notes as a single source for user-visible changes. + +## 7) Optional manual triggers + +- For staging-only release runs, use `workflow_dispatch` with explicit version and + `mac_arm64_only` preference. +- For normal full desktop releases, set `mac_arm64_only=false` only when x64 macOS, + Linux, and Windows artifacts are all required.