From abc3775519902fa56be0fcc5c14229b571fbcf4d Mon Sep 17 00:00:00 2001 From: untra Date: Mon, 14 Sep 2026 18:10:32 -0600 Subject: [PATCH 1/2] storybook applied --- .github/workflows/build.yaml | 122 +++--- .gitignore | 6 +- .oxlintrc.jsonc | 17 + Cargo.lock | 2 +- Cargo.toml | 2 +- Makefile | 7 +- VERSION | 2 +- agnt-plugin/manifest.json | 2 +- agnt-plugin/package.json | 2 +- bump-version.sh | 86 ---- charts/operator/Chart.yaml | 4 +- coder-module/main.tf | 5 +- docs/_config.yml | 2 +- docs/assets/css/tokens.css | 2 + docs/schemas/openapi.json | 2 +- opr8r/Cargo.lock | 2 +- opr8r/Cargo.toml | 2 +- package.json | 8 +- scripts/ci/bump-version.sh | 122 ++++++ scripts/cicdprep.sh | 15 + tests/version_parity.rs | 24 +- ui/src/Layout.tsx | 159 +++---- ui/src/components/BrandIcon.tsx | 13 +- ui/src/components/ConceptIcon.tsx | 16 +- ui/src/components/KanbanBoard.tsx | 102 +---- ui/src/components/PageHeader.tsx | 29 +- ui/src/components/SectionCard.tsx | 88 +--- ui/src/components/TicketDetailPanel.tsx | 282 ++++++------- ui/src/index.css | 47 +-- ui/src/routes/DashboardPage.module.css | 73 ---- ui/src/routes/DashboardPage.tsx | 77 ++-- ui/src/routes/DevicePage.tsx | 102 +++-- ui/src/routes/ForgotPasswordPage.tsx | 82 ++-- ui/src/routes/IssueTypesPage.module.css | 181 -------- ui/src/routes/IssueTypesPage.tsx | 155 +++---- ui/src/routes/LoginPage.tsx | 123 +++--- ui/src/routes/QueuePage.module.css | 105 ----- ui/src/routes/QueuePage.tsx | 57 +-- ui/src/routes/ResetPasswordPage.tsx | 169 ++++---- ui/src/routes/SetupPage.tsx | 177 ++++---- .../onboarding/OnboardingPage.module.css | 5 - ui/src/routes/onboarding/steps.tsx | 59 +-- ui/vite.config.ts | 16 +- vscode-extension/package.json | 2 +- vscode-extension/src/webhook-server.ts | 2 +- webcomponents/.storybook/icon-assets.ts | 42 ++ webcomponents/.storybook/main.ts | 14 + webcomponents/.storybook/preview.css | 85 ++++ webcomponents/.storybook/preview.tsx | 44 ++ webcomponents/.storybook/vitest.setup.ts | 23 + webcomponents/README.md | 16 +- webcomponents/bun.lock | 392 +++++++++++++++++- webcomponents/package.json | 14 +- .../src/components/AppShell.module.css | 73 +--- webcomponents/src/components/AppShell.tsx | 141 +++++++ .../src/components/AsyncState.module.css | 12 + webcomponents/src/components/AsyncState.tsx | 32 ++ .../src/components/AuthCard.module.css | 6 +- webcomponents/src/components/AuthCard.tsx | 93 +++++ webcomponents/src/components/BrandIcon.tsx | 18 + .../src/components/Choice.module.css | 34 ++ webcomponents/src/components/Choice.tsx | 41 ++ webcomponents/src/components/ConceptIcon.tsx | 9 + .../src/components/KanbanBoard.module.css | 65 ++- webcomponents/src/components/KanbanBoard.tsx | 109 +++++ .../src/components/LaunchForm.module.css | 64 +++ webcomponents/src/components/LaunchForm.tsx | 111 +++++ .../src/components/PageHeader.module.css | 10 +- webcomponents/src/components/PageHeader.tsx | 27 ++ .../src/components/RightPanel.module.css | 25 ++ webcomponents/src/components/RightPanel.tsx | 28 ++ .../src/components/SectionCard.module.css | 70 ++-- webcomponents/src/components/SectionCard.tsx | 91 ++++ .../components/TicketDetailView.module.css | 12 + .../src/components/TicketDetailView.tsx | 50 +++ webcomponents/src/elements.css | 14 +- .../elements/operator-workflow-explorer.tsx | 63 +-- webcomponents/src/index.ts | 66 ++- webcomponents/src/styles/semantic.css | 43 ++ .../src/views/DashboardView.module.css | 11 + webcomponents/src/views/DashboardView.tsx | 67 +++ .../src/views/IssueTypesView.module.css | 30 ++ webcomponents/src/views/IssueTypesView.tsx | 125 ++++++ webcomponents/src/views/QueueView.module.css | 2 + webcomponents/src/views/QueueView.tsx | 38 ++ .../src/views/WorkflowExplorerView.tsx | 95 +++++ webcomponents/src/vite-env.d.ts | 1 + webcomponents/src/workflow/WorkflowGraph.tsx | 4 +- .../stories/components/AppShell.stories.tsx | 114 +++++ .../stories/components/AsyncState.stories.tsx | 55 +++ .../stories/components/AuthCard.stories.tsx | 113 +++++ .../stories/components/BrandIcon.stories.tsx | 41 ++ .../stories/components/Choice.stories.tsx | 57 +++ .../components/ConceptIcon.stories.tsx | 26 ++ .../components/KanbanBoard.stories.tsx | 36 ++ .../stories/components/LaunchForm.stories.tsx | 62 +++ .../stories/components/PageHeader.stories.tsx | 32 ++ .../stories/components/RightPanel.stories.tsx | 41 ++ .../components/SectionCard.stories.tsx | 68 +++ .../components/TicketDetailView.stories.tsx | 67 +++ .../components/WorkflowGraph.stories.tsx | 33 ++ webcomponents/stories/fixtures/operator.ts | 371 +++++++++++++++++ .../stories/pages/DashboardView.stories.tsx | 47 +++ .../stories/pages/IssueTypesView.stories.tsx | 77 ++++ .../stories/pages/QueueView.stories.tsx | 76 ++++ .../views/WorkflowExplorerView.stories.tsx | 50 +++ webcomponents/tsconfig.storybook.json | 7 + webcomponents/vite.react.config.ts | 1 + webcomponents/vitest.config.ts | 32 ++ zed-extension/Cargo.lock | 2 +- zed-extension/Cargo.toml | 2 +- zed-extension/extension.toml | 2 +- 112 files changed, 4435 insertions(+), 1844 deletions(-) delete mode 100755 bump-version.sh create mode 100755 scripts/ci/bump-version.sh delete mode 100644 ui/src/routes/DashboardPage.module.css delete mode 100644 ui/src/routes/IssueTypesPage.module.css delete mode 100644 ui/src/routes/QueuePage.module.css create mode 100644 webcomponents/.storybook/icon-assets.ts create mode 100644 webcomponents/.storybook/main.ts create mode 100644 webcomponents/.storybook/preview.css create mode 100644 webcomponents/.storybook/preview.tsx create mode 100644 webcomponents/.storybook/vitest.setup.ts rename ui/src/Layout.module.css => webcomponents/src/components/AppShell.module.css (68%) create mode 100644 webcomponents/src/components/AppShell.tsx create mode 100644 webcomponents/src/components/AsyncState.module.css create mode 100644 webcomponents/src/components/AsyncState.tsx rename ui/src/routes/AuthPage.module.css => webcomponents/src/components/AuthCard.module.css (95%) create mode 100644 webcomponents/src/components/AuthCard.tsx create mode 100644 webcomponents/src/components/BrandIcon.tsx create mode 100644 webcomponents/src/components/Choice.module.css create mode 100644 webcomponents/src/components/Choice.tsx create mode 100644 webcomponents/src/components/ConceptIcon.tsx rename {ui => webcomponents}/src/components/KanbanBoard.module.css (67%) create mode 100644 webcomponents/src/components/KanbanBoard.tsx create mode 100644 webcomponents/src/components/LaunchForm.module.css create mode 100644 webcomponents/src/components/LaunchForm.tsx rename {ui => webcomponents}/src/components/PageHeader.module.css (90%) create mode 100644 webcomponents/src/components/PageHeader.tsx create mode 100644 webcomponents/src/components/RightPanel.module.css create mode 100644 webcomponents/src/components/RightPanel.tsx rename {ui => webcomponents}/src/components/SectionCard.module.css (68%) create mode 100644 webcomponents/src/components/SectionCard.tsx create mode 100644 webcomponents/src/components/TicketDetailView.module.css create mode 100644 webcomponents/src/components/TicketDetailView.tsx create mode 100644 webcomponents/src/styles/semantic.css create mode 100644 webcomponents/src/views/DashboardView.module.css create mode 100644 webcomponents/src/views/DashboardView.tsx create mode 100644 webcomponents/src/views/IssueTypesView.module.css create mode 100644 webcomponents/src/views/IssueTypesView.tsx create mode 100644 webcomponents/src/views/QueueView.module.css create mode 100644 webcomponents/src/views/QueueView.tsx create mode 100644 webcomponents/src/views/WorkflowExplorerView.tsx create mode 100644 webcomponents/src/vite-env.d.ts create mode 100644 webcomponents/stories/components/AppShell.stories.tsx create mode 100644 webcomponents/stories/components/AsyncState.stories.tsx create mode 100644 webcomponents/stories/components/AuthCard.stories.tsx create mode 100644 webcomponents/stories/components/BrandIcon.stories.tsx create mode 100644 webcomponents/stories/components/Choice.stories.tsx create mode 100644 webcomponents/stories/components/ConceptIcon.stories.tsx create mode 100644 webcomponents/stories/components/KanbanBoard.stories.tsx create mode 100644 webcomponents/stories/components/LaunchForm.stories.tsx create mode 100644 webcomponents/stories/components/PageHeader.stories.tsx create mode 100644 webcomponents/stories/components/RightPanel.stories.tsx create mode 100644 webcomponents/stories/components/SectionCard.stories.tsx create mode 100644 webcomponents/stories/components/TicketDetailView.stories.tsx create mode 100644 webcomponents/stories/components/WorkflowGraph.stories.tsx create mode 100644 webcomponents/stories/fixtures/operator.ts create mode 100644 webcomponents/stories/pages/DashboardView.stories.tsx create mode 100644 webcomponents/stories/pages/IssueTypesView.stories.tsx create mode 100644 webcomponents/stories/pages/QueueView.stories.tsx create mode 100644 webcomponents/stories/views/WorkflowExplorerView.stories.tsx create mode 100644 webcomponents/tsconfig.storybook.json create mode 100644 webcomponents/vitest.config.ts diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 09b82312..ae374bff 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -75,6 +75,14 @@ jobs: (cd ui && bun install --frozen-lockfile) (cd vscode-extension && npm ci && npm run copy-types) + - name: Build shared web components + run: | + cd webcomponents + bun install --frozen-lockfile + bun run typecheck + bun test + bun run build + - name: Format and lint frontend run: | bun run fmt:check @@ -87,14 +95,6 @@ jobs: - name: Lint shell scripts run: shellcheck -S warning scripts/*.sh scripts/ci/*.sh .githooks/* - - name: Build shared web components - run: | - cd webcomponents - bun install --frozen-lockfile - bun run typecheck - bun test - bun run build - - name: Build UI dist run: | cd ui @@ -181,6 +181,54 @@ jobs: severity: HIGH,CRITICAL exit-code: '1' + # Storybook renders every story in a real browser and runs axe over it. + # Deliberately a peer of lint-test with no `needs:`, so it stays a required PR check + storybook: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Install toolchain + uses: dtolnay/rust-toolchain@1.95 + + - name: Cache cargo + uses: actions/cache@v6 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-storybook-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-storybook- + ${{ runner.os }}-cargo- + + # The stories are typed against these; generate before they compile. + # lint-test owns the "bindings are committed and fresh" gate. + - name: Generate TypeScript bindings + run: cargo test --locked export_bindings_ + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.14 + + - name: Install dependencies + run: cd webcomponents && bun install --frozen-lockfile + + - name: Typecheck stories + run: cd webcomponents && bun run typecheck:stories + + - name: Install Chromium + run: cd webcomponents && bunx playwright install --with-deps chromium + + # Keep byte-identical to the `storybook` Makefile target. + - name: Build and test Storybook + run: | + cd webcomponents + bun run storybook:build + bun run test:storybook + # Compute the next version once, before anything is built, so the compiled # binaries embed the same version that the release tag + Docker tag will use. version: @@ -211,6 +259,9 @@ jobs: with: components: llvm-tools-preview + - name: Set up Helm + uses: azure/setup-helm@v5.0.1 + - name: Cache cargo uses: actions/cache@v6 with: @@ -562,54 +613,13 @@ jobs: path: artifacts # Version was computed in the `version` job and already baked into the - # built binaries; persist it into the tracked manifests for the commit/tag. - - name: Write VERSION file - run: echo "${{ needs.version.outputs.version }}" > VERSION - - - name: Update Cargo.toml version - run: | - sed -i 's/^version = ".*"/version = "${{ needs.version.outputs.version }}"/' Cargo.toml - - - name: Update Cargo.lock - run: cargo update --workspace - - - name: Update docs/_config.yml version - run: | - sed -i 's/^version: .*/version: ${{ needs.version.outputs.version }}/' docs/_config.yml - - - name: Update Helm chart versions - run: | - sed -i 's/^version: .*/version: ${{ needs.version.outputs.version }}/' charts/operator/Chart.yaml - sed -i 's/^appVersion: .*/appVersion: "${{ needs.version.outputs.version }}"/' charts/operator/Chart.yaml - - - name: Update package.json versions - run: | - for f in vscode-extension/package.json \ - agnt-plugin/package.json \ - agnt-plugin/manifest.json; do - jq --arg v "${{ needs.version.outputs.version }}" '.version = $v' "$f" > tmp.json && mv tmp.json "$f" - done - - - name: Update opr8r Cargo.toml version - run: | - sed -i 's/^version = ".*"/version = "${{ needs.version.outputs.version }}"/' opr8r/Cargo.toml - cd opr8r && cargo update --workspace - - - name: Update zed-extension versions - run: | - sed -i 's/^version = ".*"/version = "${{ needs.version.outputs.version }}"/' zed-extension/Cargo.toml - sed -i 's/^version = ".*"/version = "${{ needs.version.outputs.version }}"/' zed-extension/extension.toml - cd zed-extension && cargo update -p operator-zed - - - name: Update TypeScript VERSION constant - run: | - sed -i "s/const VERSION = [\"'][^\"']*[\"']/const VERSION = \\"${{ needs.version.outputs.version }}\\"/" vscode-extension/src/webhook-server.ts - - # Pinned by tests/version_parity.rs; the range keeps the sed inside the - # install_version block so sibling variable defaults are untouched. - - name: Update coder-module install_version default - run: | - sed -i '/variable "install_version"/,/^}/ s/^\( default *= *\)"[^"]*"/\1"${{ needs.version.outputs.version }}"/' coder-module/main.tf + # built binaries; one script revs every tracked manifest and verifies each + # one landed, so a bad rewrite fails here instead of half-tagging a release. + # The script can be run locally for manual version revs. + - name: Bump versioned manifests + env: + VERSION: ${{ needs.version.outputs.version }} + run: ./scripts/ci/bump-version.sh "$VERSION" # openapi.json's version is code-derived (env!("CARGO_PKG_VERSION")); the # already-built linux binary embeds the new version, so regenerate the diff --git a/.gitignore b/.gitignore index dbc08f77..6653ad01 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ zed-extension/target/ # typescript/node build artifacts dist/ node_modules/ +webcomponents/storybook-static/ # Shared web-component bundle copied into the docs site by `make docs`. # Built from webcomponents/ ahead of the Jekyll build; never committed. @@ -58,4 +59,7 @@ test-output.txt # terraform working files left by `bun test` in coder-module/ coder-module/.terraform/ coder-module/.terraform.lock.hcl -coder-module/terraform.tfstate* \ No newline at end of file +coder-module/terraform.tfstate* + +# superpowers and agent internal notes +superpowers/ \ No newline at end of file diff --git a/.oxlintrc.jsonc b/.oxlintrc.jsonc index 80ca2f4b..fdbc54cc 100644 --- a/.oxlintrc.jsonc +++ b/.oxlintrc.jsonc @@ -191,6 +191,23 @@ "files": ["webcomponents/src/**/*.ts", "webcomponents/src/**/*.tsx"], "env": { "browser": true, "es2022": true } }, + { + "files": [ + "webcomponents/stories/**/*.ts", + "webcomponents/stories/**/*.tsx", + "webcomponents/.storybook/preview.tsx", + "webcomponents/.storybook/vitest.setup.ts" + ], + "env": { "browser": true, "es2022": true } + }, + { + "files": [ + "webcomponents/.storybook/main.ts", + "webcomponents/.storybook/icon-assets.ts", + "webcomponents/vitest.config.ts" + ], + "env": { "node": true, "es2022": true } + }, { "files": ["webcomponents/src/**/*.test.ts", "webcomponents/src/**/*.test.tsx"], "globals": { "Bun": "readonly" }, diff --git a/Cargo.lock b/Cargo.lock index edd0dc61..a5c3e0fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2606,7 +2606,7 @@ dependencies = [ [[package]] name = "operator" -version = "0.2.10" +version = "0.2.11" dependencies = [ "agent-client-protocol", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index a5b0ea6e..9b492f94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "operator" -version = "0.2.10" +version = "0.2.11" edition = "2021" rust-version = "1.95" description = "Multi-agent orchestration dashboard for kanban shaped software development" diff --git a/Makefile b/Makefile index dfaa0870..d9abe6f3 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ # a clean CI run. `make install-hooks` wires the committed pre-push hook, which # runs the fast lint gate (fmt + clippy, no tests) before every push. -.PHONY: check fmt clippy test build run install-hooks bindings webcomponents ui docs \ +.PHONY: check fmt clippy test build run install-hooks bindings webcomponents storybook ui docs \ fmt-ts lint-ts lint-shell relay # Full CI-parity gate. Keep these commands byte-identical to @@ -64,6 +64,11 @@ bindings: webcomponents: bindings cd webcomponents && bun install --frozen-lockfile && bun run typecheck && bun test && bun run build +# Deterministic visual fixtures consumed locally by Storybook and later by Pixel. +storybook: webcomponents + cd webcomponents && bun run typecheck:stories + cd webcomponents && bun run storybook:build && bun run test:storybook + # The embedded SPA, which resolves @operator/webcomponents from its dist/. ui: webcomponents cd ui && bun install --frozen-lockfile && bun run build diff --git a/VERSION b/VERSION index 13dead7e..d3b5ba4b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.10 +0.2.11 diff --git a/agnt-plugin/manifest.json b/agnt-plugin/manifest.json index 0c7af7c4..c7f01323 100644 --- a/agnt-plugin/manifest.json +++ b/agnt-plugin/manifest.json @@ -1,6 +1,6 @@ { "name": "operator-plugin", - "version": "0.2.10", + "version": "0.2.11", "description": "Orchestrate Operator! ticket-driven coding agents from AGNT workflows", "author": "untra", "displayName": "Operator!", diff --git a/agnt-plugin/package.json b/agnt-plugin/package.json index 5dcba2e8..dd507f17 100644 --- a/agnt-plugin/package.json +++ b/agnt-plugin/package.json @@ -1,6 +1,6 @@ { "name": "operator-plugin", - "version": "0.2.10", + "version": "0.2.11", "description": "Orchestrate Operator! ticket-driven coding agents from AGNT workflows", "author": "untra", "license": "MIT", diff --git a/bump-version.sh b/bump-version.sh deleted file mode 100755 index e82e1f6a..00000000 --- a/bump-version.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" - -DRY_RUN=false -if [[ "${1:-}" == "--dry-run" ]]; then - DRY_RUN=true -fi - -# Read current version from source of truth -CURRENT="$(tr -d '[:space:]' < VERSION)" -IFS='.' read -r major minor patch <<< "$CURRENT" -NEW="$major.$minor.$((patch + 1))" - -echo "Bumping version: $CURRENT -> $NEW" - -# Portable first-occurrence replacement using awk -# Replaces only the first line containing the old string -replace_first() { - local file="$1" old="$2" new="$3" - awk -v old="$old" -v new="$new" \ - '!done && index($0, old) { sub(old, new); done=1 } 1' \ - "$file" > "$file.tmp" && mv "$file.tmp" "$file" -} - -# Text files: replace first occurrence of version string -TEXT_FILES=( - "VERSION" - "Cargo.toml" - "opr8r/Cargo.toml" - "zed-extension/Cargo.toml" - "zed-extension/extension.toml" - "vscode-extension/src/webhook-server.ts" - "docs/_config.yml" - "coder-module/main.tf" -) - -# JSON files: update .version via jq -JSON_FILES=( - "vscode-extension/package.json" - "agnt-plugin/package.json" - "agnt-plugin/manifest.json" -) - -for f in "${TEXT_FILES[@]}"; do - if [[ ! -f "$f" ]]; then - echo "WARNING: $f not found, skipping" - continue - fi - if $DRY_RUN; then - echo "[dry-run] would update $f" - else - replace_first "$f" "$CURRENT" "$NEW" - echo "Updated $f" - fi -done - -for f in "${JSON_FILES[@]}"; do - if [[ ! -f "$f" ]]; then - echo "WARNING: $f not found, skipping" - continue - fi - if $DRY_RUN; then - echo "[dry-run] would update $f" - else - jq --arg v "$NEW" '.version = $v' "$f" > "$f.tmp" && mv "$f.tmp" "$f" - echo "Updated $f" - fi -done - -CHART_FILE="charts/operator/Chart.yaml" -if $DRY_RUN; then - echo "[dry-run] would update $CHART_FILE" -else - awk -v version="$NEW" ' - /^version:/ { $0 = "version: " version } - /^appVersion:/ { $0 = "appVersion: \"" version "\"" } - { print } - ' "$CHART_FILE" > "$CHART_FILE.tmp" && mv "$CHART_FILE.tmp" "$CHART_FILE" - echo "Updated $CHART_FILE" -fi - -echo "" -echo "Done. Version is now $NEW" diff --git a/charts/operator/Chart.yaml b/charts/operator/Chart.yaml index b88a16ac..9c1af8cd 100644 --- a/charts/operator/Chart.yaml +++ b/charts/operator/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: operator description: Run Operator as a single-writer agent orchestration service type: application -version: 0.2.10 -appVersion: "0.2.10" +version: 0.2.11 +appVersion: "0.2.11" kubeVersion: ">=1.25.0-0" home: https://operator.untra.io sources: diff --git a/coder-module/main.tf b/coder-module/main.tf index be49bd6e..bb702fac 100644 --- a/coder-module/main.tf +++ b/coder-module/main.tf @@ -32,13 +32,10 @@ variable "slug" { default = "operator" } -# `default` is revved by bump-version.sh and pinned to the VERSION file by -# tests/version_parity.rs -- an unbumped tag points workspaces at a -# nonexistent GitHub release. variable "install_version" { type = string description = "The version of operator to install (must match a GitHub release tag)." - default = "0.2.10" + default = "0.2.11" } variable "install_prefix" { diff --git a/docs/_config.yml b/docs/_config.yml index 5ed75923..ffa3c151 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -47,7 +47,7 @@ collections_dir: . # Permalink structure permalink: pretty -version: 0.2.10 +version: 0.2.11 # Google Analytics ga_tag: G-5JZPJWWT7S # Replace with actual GA4 measurement ID from analytics.google.com diff --git a/docs/assets/css/tokens.css b/docs/assets/css/tokens.css index 05e3a228..5ededbd8 100644 --- a/docs/assets/css/tokens.css +++ b/docs/assets/css/tokens.css @@ -13,6 +13,7 @@ :root { /* Brand palette */ --color-salmon: #e05d44; /* Terracotta : primary brand */ + --color-salmon-text: #a33a29; /* Terracotta, darkened for text on --color-bg */ --color-cornflower: #6688aa; /* Muted blue : secondary text / separators */ --color-cream: #f2eac9; /* Warm accent / highlights */ --color-coral: #e05d44; /* Link / accent (alias of salmon in light) */ @@ -32,6 +33,7 @@ [data-theme="dark"] { --color-salmon: #e05d44; + --color-salmon-text: #ff8a76; --color-cornflower: #88aabb; --color-cream: #1a1d1e; --color-coral: #ff7a66; diff --git a/docs/schemas/openapi.json b/docs/schemas/openapi.json index 00329a77..0b336ded 100644 --- a/docs/schemas/openapi.json +++ b/docs/schemas/openapi.json @@ -10,7 +10,7 @@ "license": { "name": "MIT" }, - "version": "0.2.10" + "version": "0.2.11" }, "paths": { "/api/v1/agents/active": { diff --git a/opr8r/Cargo.lock b/opr8r/Cargo.lock index 98f79350..e9d6a863 100644 --- a/opr8r/Cargo.lock +++ b/opr8r/Cargo.lock @@ -779,7 +779,7 @@ dependencies = [ [[package]] name = "opr8r" -version = "0.2.10" +version = "0.2.11" dependencies = [ "clap", "operator-relay", diff --git a/opr8r/Cargo.toml b/opr8r/Cargo.toml index 1dbe8672..bd2d991e 100644 --- a/opr8r/Cargo.toml +++ b/opr8r/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "opr8r" -version = "0.2.10" +version = "0.2.11" edition = "2021" description = "Minimal CLI wrapper for LLM commands in multi-step ticket workflows" license = "MIT" diff --git a/package.json b/package.json index 5f7a84d7..5a32d0f5 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,12 @@ "docs:typescript": "typedoc", "lint": "oxlint --type-aware", "lint:ui": "oxlint --type-aware ui/src", - "lint:webcomponents": "oxlint --type-aware webcomponents/src", + "lint:webcomponents": "oxlint --type-aware --tsconfig=webcomponents/tsconfig.storybook.json webcomponents/src webcomponents/stories webcomponents/.storybook webcomponents/vitest.config.ts", "lint:vscode": "oxlint --type-aware vscode-extension/src vscode-extension/test vscode-extension/webview-ui", - "fmt": "oxfmt --write \"ui/src/**/*.{ts,tsx}\" \"webcomponents/src/**/*.{ts,tsx}\" \"webcomponents/scripts/**/*.mjs\" \"vscode-extension/src/**/*.ts\" \"vscode-extension/test/**/*.ts\" \"vscode-extension/webview-ui/**/*.{ts,tsx}\" \"vscode-extension/scripts/**/*.js\" \"agnt-plugin/**/*.js\" \"coder-module/*.ts\"", - "fmt:check": "oxfmt --check \"ui/src/**/*.{ts,tsx}\" \"webcomponents/src/**/*.{ts,tsx}\" \"webcomponents/scripts/**/*.mjs\" \"vscode-extension/src/**/*.ts\" \"vscode-extension/test/**/*.ts\" \"vscode-extension/webview-ui/**/*.{ts,tsx}\" \"vscode-extension/scripts/**/*.js\" \"agnt-plugin/**/*.js\" \"coder-module/*.ts\"", + "fmt": "oxfmt --write \"ui/src/**/*.{ts,tsx}\" \"webcomponents/src/**/*.{ts,tsx}\" \"webcomponents/stories/**/*.{ts,tsx}\" \"webcomponents/.storybook/**/*.{ts,tsx}\" \"webcomponents/vitest.config.ts\" \"webcomponents/scripts/**/*.mjs\" \"vscode-extension/src/**/*.ts\" \"vscode-extension/test/**/*.ts\" \"vscode-extension/webview-ui/**/*.{ts,tsx}\" \"vscode-extension/scripts/**/*.js\" \"agnt-plugin/**/*.js\" \"coder-module/*.ts\"", + "fmt:check": "oxfmt --check \"ui/src/**/*.{ts,tsx}\" \"webcomponents/src/**/*.{ts,tsx}\" \"webcomponents/stories/**/*.{ts,tsx}\" \"webcomponents/.storybook/**/*.{ts,tsx}\" \"webcomponents/vitest.config.ts\" \"webcomponents/scripts/**/*.mjs\" \"vscode-extension/src/**/*.ts\" \"vscode-extension/test/**/*.ts\" \"vscode-extension/webview-ui/**/*.{ts,tsx}\" \"vscode-extension/scripts/**/*.js\" \"agnt-plugin/**/*.js\" \"coder-module/*.ts\"", "fmt:ui": "oxfmt --write \"ui/src/**/*.{ts,tsx}\"", - "fmt:webcomponents": "oxfmt --write \"webcomponents/src/**/*.{ts,tsx}\" \"webcomponents/scripts/**/*.mjs\"", + "fmt:webcomponents": "oxfmt --write \"webcomponents/src/**/*.{ts,tsx}\" \"webcomponents/stories/**/*.{ts,tsx}\" \"webcomponents/.storybook/**/*.{ts,tsx}\" \"webcomponents/vitest.config.ts\" \"webcomponents/scripts/**/*.mjs\"", "fmt:vscode": "oxfmt --write \"vscode-extension/src/**/*.ts\" \"vscode-extension/test/**/*.ts\" \"vscode-extension/webview-ui/**/*.{ts,tsx}\" \"vscode-extension/scripts/**/*.js\"", "lint:agnt": "oxlint agnt-plugin", "lint:coder-module": "oxlint --type-aware coder-module" diff --git a/scripts/ci/bump-version.sh b/scripts/ci/bump-version.sh new file mode 100755 index 00000000..c6d17136 --- /dev/null +++ b/scripts/ci/bump-version.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# Rev every tracked manifest to a version, then verify each rewrite landed. +# Used by the release job in .github/workflows/build.yaml and for manual bumps. +# Keep the file list in sync with MANAGED in tests/version_parity.rs. +# +# bump-version.sh # patch bump of the VERSION file +# bump-version.sh 1.2.3 # explicit version +# bump-version.sh --dry-run # report without writing +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT" + +DRY_RUN=false +if [[ "${1:-}" == "--dry-run" ]]; then + DRY_RUN=true + shift +fi + +CURRENT="$(tr -d '[:space:]' ]" >&2 + exit 2 +fi + +echo "Bumping version: $CURRENT -> $VERSION" + +# Replace the first line matching a regex. Anchored line rewrites keep an +# indented `version = ` in a dependency table from being mistaken for the +# package version. +replace_first() { + local file="$1" re="$2" line="$3" tmp + if $DRY_RUN; then + echo "[dry-run] $file: $line" + return + fi + tmp="$(mktemp)" + awk -v re="$re" -v line="$line" \ + '!done && $0 ~ re { $0 = line; done = 1 } 1' "$file" >"$tmp" + mv "$tmp" "$file" +} + +set_json_version() { + local file="$1" tmp + if $DRY_RUN; then + echo "[dry-run] $file: \"version\": \"$VERSION\"" + return + fi + tmp="$(mktemp)" + jq --arg v "$VERSION" '.version = $v' "$file" >"$tmp" + mv "$tmp" "$file" +} + +if $DRY_RUN; then + echo "[dry-run] VERSION: $VERSION" +else + echo "$VERSION" >VERSION +fi + +for f in Cargo.toml opr8r/Cargo.toml zed-extension/Cargo.toml zed-extension/extension.toml; do + replace_first "$f" '^version = "' "version = \"$VERSION\"" +done + +replace_first docs/_config.yml '^version:' "version: $VERSION" +replace_first charts/operator/Chart.yaml '^version:' "version: $VERSION" +replace_first charts/operator/Chart.yaml '^appVersion:' "appVersion: \"$VERSION\"" +replace_first vscode-extension/src/webhook-server.ts \ + '^const VERSION = ' "const VERSION = \"$VERSION\";" + +for f in vscode-extension/package.json agnt-plugin/package.json agnt-plugin/manifest.json; do + set_json_version "$f" +done + +# Range-scoped so sibling variable defaults in the module are untouched. +if $DRY_RUN; then + echo "[dry-run] coder-module/main.tf: install_version default = \"$VERSION\"" +else + tmp="$(mktemp)" + sed "/variable \"install_version\"/,/^}/ s/^\( default *= *\)\"[^\"]*\"/\1\"$VERSION\"/" \ + coder-module/main.tf >"$tmp" + mv "$tmp" coder-module/main.tf +fi + +if $DRY_RUN; then + echo "[dry-run] cargo lockfiles" + exit 0 +fi + +cargo update --workspace --quiet +(cd opr8r && cargo update --workspace --quiet) +(cd zed-extension && cargo update -p operator-zed --quiet) + +# Fail loudly here rather than leaving a half-revved release commit. +failed=0 +check() { + grep -qF "$2" "$1" || { + echo "bump-version: $1 does not carry $VERSION" >&2 + failed=1 + } +} +check VERSION "$VERSION" +for f in Cargo.toml opr8r/Cargo.toml zed-extension/Cargo.toml zed-extension/extension.toml; do + check "$f" "version = \"$VERSION\"" +done +check docs/_config.yml "version: $VERSION" +check charts/operator/Chart.yaml "version: $VERSION" +check charts/operator/Chart.yaml "appVersion: \"$VERSION\"" +for f in vscode-extension/package.json agnt-plugin/package.json agnt-plugin/manifest.json; do + check "$f" "\"version\": \"$VERSION\"" +done +check vscode-extension/src/webhook-server.ts "const VERSION = \"$VERSION\";" +check coder-module/main.tf "default = \"$VERSION\"" + +echo "Done. Version is now $VERSION" +exit "$failed" diff --git a/scripts/cicdprep.sh b/scripts/cicdprep.sh index 456d04ec..09b53846 100755 --- a/scripts/cicdprep.sh +++ b/scripts/cicdprep.sh @@ -270,6 +270,21 @@ if needs_operator; then bun run build ) && pass "Web components build" || fail "Web components build" + # Mirrors the standalone `storybook` job in build.yaml, which is a peer of + # lint-test rather than part of the release chain. + if needs_ts_webcomp; then + step "Storybook" + ( + cd webcomponents + bun run typecheck:stories + bunx playwright install --with-deps chromium + bun run storybook:build + bun run test:storybook + ) && pass "Storybook" || fail "Storybook" + else + skip "Storybook (no webcomponents changes)" + fi + step "UI build" ( cd ui diff --git a/tests/version_parity.rs b/tests/version_parity.rs index af1264ab..ca86f09e 100644 --- a/tests/version_parity.rs +++ b/tests/version_parity.rs @@ -1,8 +1,8 @@ //! Asserts every managed manifest carries the canonical version from `VERSION`. //! -//! Adding a new versioned manifest is a one-line addition to `MANAGED` below. -//! Keep this list in sync with the files revved by `bump-version.sh` and the -//! `release` job in `.github/workflows/build.yaml`. +//! Adding a new versioned manifest is a one-line addition to `MANAGED` below +//! plus a rewrite in `scripts/ci/bump-version.sh`, which the `release` job in +//! `.github/workflows/build.yaml` runs. The pairing is asserted below. use std::fs; use std::path::{Path, PathBuf}; @@ -117,7 +117,7 @@ fn test_all_managed_manifests_match_version_file() { assert!( mismatches.is_empty(), - "version drift from VERSION={expected:?}:\n{}\nRun ./bump-version.sh or correct the files above; regenerate docs/schemas/openapi.json with `cargo run -- docs --only openapi`.", + "version drift from VERSION={expected:?}:\n{}\nRun ./scripts/ci/bump-version.sh [version] or correct the files above; regenerate docs/schemas/openapi.json with `cargo run -- docs --only openapi`.", mismatches.join("\n") ); @@ -128,3 +128,19 @@ fn test_all_managed_manifests_match_version_file() { .and_then(|line| between_quotes_after(line, ":")); assert_eq!(app_version.as_deref(), Some(expected.as_str())); } + +/// The bump script is what CI runs; a manifest listed here but absent there +/// would drift silently until the next release. +#[test] +fn test_bump_script_rewrites_every_managed_manifest() { + let script = read(&repo_root().join("scripts/ci/bump-version.sh")); + let missing: Vec<_> = MANAGED + .iter() + .map(|(rel, _)| rel) + .filter(|rel| **rel != "docs/schemas/openapi.json" && !script.contains(**rel)) + .collect(); + assert!( + missing.is_empty(), + "scripts/ci/bump-version.sh does not rewrite: {missing:?}" + ); +} diff --git a/ui/src/Layout.tsx b/ui/src/Layout.tsx index c9cff0e0..deabc852 100644 --- a/ui/src/Layout.tsx +++ b/ui/src/Layout.tsx @@ -1,20 +1,23 @@ -import { useState } from "react"; +import { useCallback, useState } from "react"; import { NavLink, Outlet, useNavigate } from "react-router-dom"; -import styles from "./Layout.module.css"; +import { + AppShell, + BrandName, + NavGroup, + NavRow, + RightPanel as RightPanelView, + SignOutButton, + ThemeToggle, +} from "@operator/webcomponents"; import { useTheme } from "./theme"; import type { Concept } from "./concepts"; import { CONCEPTS, STATUS_KEYS, PAGE_KEYS } from "./concepts"; -import { ConceptIcon } from "./components/ConceptIcon"; import { SectionsProvider, useSections } from "./sections-context"; import { RightPanelProvider, useRightPanel } from "./right-panel"; import type { SectionDto } from "./api-client"; import { OperatorApi, setCsrfToken } from "./api-client"; import { useHost } from "./host"; -function navLinkClassName({ isActive }: { isActive: boolean }): string { - return isActive ? `${styles.navLink} ${styles.active}` : styles.navLink; -} - // The "Status" group mirrors the canonical section order shared with the TUI and // VS Code extension (the SectionId enum in src/ui/status_panel.rs) and reflects // each section's live health from GET /api/v1/sections. A section whose @@ -22,81 +25,60 @@ function navLinkClassName({ isActive }: { isActive: boolean }): string { // needs - the user sees it exists and why it isn't reachable. "Pages" are // web-only views (Dashboard, Queue) with no section analog. -function NavRow({ concept, section }: { concept: Concept; section?: SectionDto }) { - const met = section ? section.met : true; - - const inner = ( - <> - - {concept.label} - {section && } - +function ConceptNavRow({ concept, section }: { concept: Concept; section?: SectionDto }) { + const renderLink = useCallback( + (content: React.ReactNode, className: (state: { isActive: boolean }) => string) => ( + + {content} + + ), + [concept.route], ); - if (!met) { - const needs = (section?.prerequisites ?? []).map((id) => CONCEPTS[id]?.label ?? id).join(", "); + if (section && !section.met) { + const needs = section.prerequisites.map((id) => CONCEPTS[id]?.label ?? id).join(", "); return ( - - {inner} - + ); } return ( - - {inner} - + ); } -function NavGroup({ label, keys }: { label: string; keys: readonly string[] }) { +function ConceptNavGroup({ label, keys }: { label: string; keys: readonly string[] }) { const { sections } = useSections(); return ( -
-

{label}

-
    - {keys.map((key) => { - const concept = CONCEPTS[key]; - const section = sections?.find((s) => s.id === key); - return ( -
  • - -
  • - ); - })} -
-
+ + {keys.map((key) => ( +
  • + s.id === key)} /> +
  • + ))} +
    ); } -// The detail sidepanel. Renders nothing until a view opens it via -// useRightPanel().open(...); when content is present it slides in on the right -// with a header (title + close) above the caller-supplied node. -function RightPanel() { +function RightPanelController() { const { content, title, close } = useRightPanel(); if (!content) { return null; } return ( - + + {content} + ); } @@ -107,7 +89,7 @@ export function Layout() { const [signingOut, setSigningOut] = useState(false); const [signOutError, setSignOutError] = useState(false); - async function signOut() { + const signOut = useCallback(async () => { setSigningOut(true); setSignOutError(false); try { @@ -121,42 +103,29 @@ export function Layout() { } finally { setSigningOut(false); } - } + }, [host, navigate]); return ( -
    - -
    - -
    - -
    + + + + + } + groups={ + <> + + + + } + footer={} + panel={} + > + +
    ); diff --git a/ui/src/components/BrandIcon.tsx b/ui/src/components/BrandIcon.tsx index 97e95f33..db95468e 100644 --- a/ui/src/components/BrandIcon.tsx +++ b/ui/src/components/BrandIcon.tsx @@ -4,20 +4,13 @@ // `brand_icon` field. Unlike `ConceptIcon` (a recolorable codicon font glyph), // brand logos are full-color SVGs, so they don't inherit `currentColor`. +import { BrandIcon as SharedBrandIcon } from "@operator/webcomponents"; + interface BrandIconProps { name: string; className?: string; } export function BrandIcon({ name, className }: BrandIconProps) { - return ( - - ); + return ; } diff --git a/ui/src/components/ConceptIcon.tsx b/ui/src/components/ConceptIcon.tsx index 9f846ffe..902bab7f 100644 --- a/ui/src/components/ConceptIcon.tsx +++ b/ui/src/components/ConceptIcon.tsx @@ -1,15 +1 @@ -// Renders a single codicon glyph. `name` is a codicon name without the -// `codicon-` prefix (e.g. "git-branch"); see ui/src/concepts.ts for the -// canonical concept→icon mapping. The font is loaded once in main.tsx via -// `@vscode/codicons/dist/codicon.css`. Icons inherit `currentColor`, so they -// recolor with surrounding text in both light and dark themes. - -interface ConceptIconProps { - name: string; - className?: string; -} - -export function ConceptIcon({ name, className }: ConceptIconProps) { - const cls = className ? `codicon codicon-${name} ${className}` : `codicon codicon-${name}`; - return