Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 23 additions & 28 deletions .github/frontend-architecture-manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ This manifest covers git-tracked, hand-written frontend files under `packages/ap

## Current State

- Manifest status: governance baseline.
- Baseline commit: `5da4d3d61` (`fix: harden frontend inventory classification`).
- Manifest status: LOC warn-only CI slice.
- Baseline commit: `c3e931935` (`chore(release): bump desktop version to v2026.5.17`).
- Default branch: `dev`.
- Master owner: [#599 UI rewrite v2](https://github.com/Astro-Han/pawwork/issues/599).
- Historical issue [#440](https://github.com/Astro-Han/pawwork/issues/440) is closed and must not be used as a new work entry.
Expand All @@ -20,10 +20,11 @@ The repeatable report command is:
```sh
bun run frontend:inventory
node script/frontend-inventory.mjs --format json
node script/frontend-inventory.mjs --check-baseline --base origin/dev --head HEAD
bun run frontend:inventory -- --format markdown --max-rows 120
```

The script is warn-only in this governance phase. It prints threshold warnings but exits successfully.
The script is warn-only in this governance phase. It prints threshold warnings for touched production frontend files above the `>500` and `>200` LOC lines, but exits successfully unless inventory generation or git diff fails.

## Report Schema

Expand Down Expand Up @@ -60,32 +61,32 @@ If this work changes from physical LOC to logical LOC or another metric, add sch

## Baseline Summary

Generated with `node script/frontend-inventory.mjs --format json` at baseline commit `5da4d3d61`.
This replaces the earlier `eb80bed96` draft baseline because the review fix made the report include root-level `src/*.ts(x)` files and stopped classifying logic-bearing `index.ts` / pure-config candidates as visibility-only inventory.
Generated with `node script/frontend-inventory.mjs --format json` at baseline commit `c3e931935`.
This replaces the earlier `5da4d3d61` baseline after the merged UI governance and owner-extraction queue changed the tracked frontend file set.

| Metric | Count |
| --- | ---: |
| Tracked `.ts` / `.tsx` files | 682 |
| Production ratchet set | 356 |
| Visibility-only inventory | 326 |
| Tracked `.ts` / `.tsx` files | 727 |
| Production ratchet set | 388 |
| Visibility-only inventory | 339 |
| Approved exceptions | 0 |
| Production files `>500` LOC | 26 |
| Production files `>200` LOC | 80 |
| Production files `>500` LOC | 19 |
| Production files `>200` LOC | 81 |
| Visibility-only files `>500` LOC | 15 |
| Visibility-only files `>200` LOC | 56 |

Production by owner lane:

| Owner lane | Files | `>200` | `>500` |
| --- | ---: | ---: | ---: |
| other/deferred | 130 | 24 | 5 |
| #638 interface audit | 7 | 1 | 0 |
| #604 settings | 21 | 9 | 3 |
| other/deferred | 136 | 24 | 5 |
| #638 interface audit | 8 | 1 | 0 |
| #604 settings | 35 | 9 | 0 |
| #599 mainline | 2 | 0 | 0 |
| #606 final shell | 45 | 14 | 6 |
| #601 message flow | 56 | 9 | 5 |
| #601 message flow | 66 | 10 | 1 |
| #605 visual shell | 84 | 18 | 6 |
| #595/#615 scroll-perf | 11 | 5 | 1 |
| #595/#615 scroll-perf | 12 | 5 | 1 |

## Owner Lanes

Expand All @@ -106,9 +107,9 @@ Closed area references are allowed as background only. [#602](https://github.com

| Stage | Rule |
| --- | --- |
| 1. Governance | Warn only. Establish baseline, schema, owner map, report command, and exception format. |
| 2. New `>500` guard | Do not add or modify an unexplained production file above 500 LOC. |
| 3. New `>200` guard | Do not add or modify a production file above 200 LOC without owner lane and manifest entry. |
| 1. Governance | Warn only. Establish baseline, schema, owner map, report command, CI inventory job, and exception format. |
| 2. New `>500` guard | Warn when a PR adds or modifies a production file above 500 LOC. Do not hard fail in the current slice. |
| 3. New `>200` guard | Warn when a PR adds or modifies a production file above 200 LOC. Do not hard fail in the current slice. |
| 4. Tighter ratchet | Only after the launch path is stable and current exceptions are reviewed. |

## Exception Schema
Expand All @@ -134,28 +135,21 @@ Current approved exceptions: none.
| LOC | Owner Lane | Status | Path |
| ---: | --- | --- | --- |
| 2463 | #606 final shell | needs-over-500-resolution | `packages/app/src/pages/layout.tsx` |
| 1217 | #601 message flow | needs-over-500-resolution | `packages/app/src/pages/session/message-timeline.tsx` |
| 1129 | #605 visual shell | needs-over-500-resolution | `packages/ui/src/components/file.tsx` |
| 1119 | #606 final shell | needs-over-500-resolution | `packages/app/src/context/layout.tsx` |
| 753 | #601 message flow | needs-over-500-resolution | `packages/ui/src/components/session-turn.tsx` |
| 733 | #604 settings | needs-over-500-resolution | `packages/app/src/components/settings-general.tsx` |
| 691 | #601 message flow | needs-over-500-resolution | `packages/app/src/pages/session.tsx` |
| 673 | other/deferred | needs-over-500-resolution | `packages/app/src/components/terminal.tsx` |
| 665 | #606 final shell | needs-over-500-resolution | `packages/app/src/context/sync.tsx` |
| 661 | #605 visual shell | needs-over-500-resolution | `packages/ui/src/components/session-review.tsx` |
| 654 | #604 settings | needs-over-500-resolution | `packages/app/src/components/dialog-connect-provider.tsx` |
| 649 | #604 settings | needs-over-500-resolution | `packages/app/src/components/dialog-select-server.tsx` |
| 634 | other/deferred | needs-over-500-resolution | `packages/app/src/addons/serialize.ts` |
| 618 | #601 message flow | needs-over-500-resolution | `packages/app/src/pages/session/use-session-commands.tsx` |
| 598 | #601 message flow | needs-over-500-resolution | `packages/ui/src/components/markdown.tsx` |
| 596 | #605 visual shell | needs-over-500-resolution | `packages/ui/src/components/line-comment-annotations.tsx` |
| 595 | #595/#615 scroll-perf | needs-over-500-resolution | `packages/app/src/pages/session/session-timeline-scroll-controller.ts` |
| 588 | #605 visual shell | needs-over-500-resolution | `packages/ui/src/components/file-icon.tsx` |
| 568 | other/deferred | needs-over-500-resolution | `packages/app/src/utils/persist.ts` |
| 560 | #606 final shell | needs-over-500-resolution | `packages/app/src/context/local.tsx` |
| 556 | #605 visual shell | needs-over-500-resolution | `packages/ui/src/theme/resolve.ts` |
| 553 | other/deferred | needs-over-500-resolution | `packages/app/src/pages/session/session-side-panel.tsx` |
| 548 | #601 message flow | needs-over-500-resolution | `packages/app/src/pages/session/use-session-commands.tsx` |
| 543 | #606 final shell | needs-over-500-resolution | `packages/app/src/context/global-sync.tsx` |
| 540 | #605 visual shell | needs-over-500-resolution | `packages/ui/src/theme/resolve.ts` |
| 527 | #605 visual shell | needs-over-500-resolution | `packages/ui/src/context/marked.tsx` |
| 511 | #606 final shell | needs-over-500-resolution | `packages/app/src/context/terminal.tsx` |
| 507 | other/deferred | needs-over-500-resolution | `packages/app/src/components/file-tree.tsx` |
Expand All @@ -170,7 +164,8 @@ bun run frontend:inventory -- --format markdown --max-rows 120

| PR | Owner lane | Base | Depends on | Boundary | Architecture effect | Verification | Status | Public write status |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Governance PR | #599 mainline / governance | `dev` | None | Manifest, schema, owner map, warn-only script, baseline report command | boundary created, owner map established, ratchet command added | `bun run frontend:inventory`, `node script/frontend-inventory.mjs --format json`, `bun run frontend:inventory -- --format markdown --max-rows 120` | in progress | PR body only |
| Governance PR | #599 mainline / governance | `dev` | None | Manifest, schema, owner map, warn-only script, baseline report command | boundary created, owner map established, ratchet command added | `bun run frontend:inventory`, `node script/frontend-inventory.mjs --format json`, `bun run frontend:inventory -- --format markdown --max-rows 120` | complete | PR body only |
| LOC warn-only CI PR | #688 governance | `dev` | None | Add CI inventory output and touched-file LOC warnings for `>500` / `>200` production frontend files | LOC governance is executable in CI; current slice warns only and does not enforce hard failure | `node script/frontend-inventory.mjs --format json`, `node script/frontend-inventory.mjs --check-baseline --base origin/dev --head HEAD`, workflow/script contract tests | in progress | PR body only |
| Contract PR | #638 interface audit | Governance branch or post-merge `dev` | Governance PR | Public contract/import boundary and compatibility checks | public contract stabilized, private import risk surfaced | typecheck plus contract-specific compatibility check | planned | PR body only |
| Message-flow PR stack | #601 message flow | post-governance `dev` unless stacked | Governance PR, maybe Contract PR if public imports move | Current launch-path message flow files only | owner extracted, LOC reduced, verification added | typecheck, unit/e2e, #600 perf gate, visual smoke | planned | PR body only |
| [#670](https://github.com/Astro-Han/pawwork/pull/670) | #601 message flow | `dev` | #667, #669 | Extract `createTimelineStaging` from `MessageTimeline` into `session-timeline-staging.ts` with browser-condition staging tests | timeline staging owner isolated; active-session message growth remains staged instead of popping to full render | focused staging/history/scroll tests, typecheck, diff check, PR CI | in review | PR body + manifest |
Expand Down
48 changes: 48 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,50 @@ jobs:
- name: lint
run: bun run lint:ci

frontend-architecture:
needs: changes
if: needs.changes.outputs.docs_only != 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false

# Load-bearing: `bun install` runs `trustedDependencies`
# postinstalls from the root package.json (electron, node-pty,
# esbuild, tree-sitter at time of writing), which call `node`
# explicitly. Do not drop without first patching those scripts.
# Source of truth: grep `trustedDependencies` in package.json. (#70)
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # actions/setup-node@v6.4.0
with:
node-version: "24"

- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # oven-sh/setup-bun@v2
with:
bun-version: "1.3.13"

- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # actions/cache@v5
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-${{ runner.os }}-

- run: bun install --frozen-lockfile

- name: frontend inventory
run: |
mkdir -p .artifacts/frontend-architecture
node script/frontend-inventory.mjs --format json > .artifacts/frontend-architecture/frontend-inventory.json

- name: LOC ratchet warnings
env:
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
HEAD_SHA: ${{ github.sha }}
run: node script/frontend-inventory.mjs --check-baseline --base "$BASE_SHA" --head "$HEAD_SHA"

unit-app:
needs: changes
if: needs.changes.outputs.docs_only != 'true'
Expand Down Expand Up @@ -430,6 +474,7 @@ jobs:
needs:
- changes
- typecheck
- frontend-architecture
- unit-ui-focused
- unit-app
- unit-opencode
Expand All @@ -440,6 +485,7 @@ jobs:
env:
DOCS_ONLY: ${{ needs.changes.outputs.docs_only }}
TYPECHECK_RESULT: ${{ needs.typecheck.result }}
FRONTEND_ARCHITECTURE_RESULT: ${{ needs['frontend-architecture'].result }}
UNIT_UI_FOCUSED_RESULT: ${{ needs['unit-ui-focused'].result }}
UNIT_APP_RESULT: ${{ needs['unit-app'].result }}
UNIT_OPENCODE_RESULT: ${{ needs['unit-opencode'].result }}
Expand All @@ -453,11 +499,13 @@ jobs:
fi

if [ "$TYPECHECK_RESULT" != "success" ] ||
[ "$FRONTEND_ARCHITECTURE_RESULT" != "success" ] ||
[ "$UNIT_UI_FOCUSED_RESULT" != "success" ] ||
[ "$UNIT_APP_RESULT" != "success" ] ||
[ "$UNIT_OPENCODE_RESULT" != "success" ] ||
[ "$UNIT_DESKTOP_RESULT" != "success" ]; then
echo "typecheck=$TYPECHECK_RESULT"
echo "frontend-architecture=$FRONTEND_ARCHITECTURE_RESULT"
echo "unit-ui-focused=$UNIT_UI_FOCUSED_RESULT"
echo "unit-app=$UNIT_APP_RESULT"
echo "unit-opencode=$UNIT_OPENCODE_RESULT"
Expand Down
46 changes: 45 additions & 1 deletion packages/opencode/test/github/ci-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const pinned = {
const runAttempt = "${{ github.run_attempt }}"
const githubSha = "${{ github.sha }}"
const lintJobName = "lint"
const frontendArchitectureJobName = "frontend-architecture"
const windowsUnitJobName = "unit-windows"

// Suffixes drive readable job and artifact names; commands use package.json names verbatim.
Expand Down Expand Up @@ -243,6 +244,39 @@ describe("ci workflow", () => {
expect(checkNeeds).not.toContain(lintJobName)
})

test("runs frontend architecture inventory as a warn-only blocking infrastructure check", () => {
const parsed = parseWorkflow(ciWorkflowPath)
const job = parsed.jobs?.[frontendArchitectureJobName]
const check = parsed.jobs?.check
const checkNeeds = Array.isArray(check?.needs) ? check.needs : []
const inventory = stepByName(frontendArchitectureJobName, "frontend inventory")
const ratchet = stepByName(frontendArchitectureJobName, "LOC ratchet warnings")

expect(job?.needs).toBe("changes")
expect(job?.if).toBe("needs.changes.outputs.docs_only != 'true'")
expect(job?.["runs-on"]).toBe("ubuntu-latest")
expect(job?.["timeout-minutes"]).toBe(10)
expect(job?.["continue-on-error"]).toBeUndefined()
expect(job?.permissions).toBeUndefined()
expect(checkoutStep(frontendArchitectureJobName)?.uses).toBe(pinned.checkout)
expect(checkoutStep(frontendArchitectureJobName)?.with?.["fetch-depth"]).toBe(0)
expect(steps(frontendArchitectureJobName).find((step) => step.uses?.startsWith("actions/setup-node@"))?.uses).toBe(
pinned.setupNode,
)
expect(steps(frontendArchitectureJobName).find((step) => step.uses?.startsWith("oven-sh/setup-bun@"))?.uses).toBe(
pinned.setupBun,
)
expect(steps(frontendArchitectureJobName).filter((step) => step.uses?.startsWith("actions/cache@")).map((step) => step.uses)).toEqual([
pinned.cache,
])
expect(inventory?.run).toContain("node script/frontend-inventory.mjs --format json")
expect(inventory?.run).toContain(".artifacts/frontend-architecture/frontend-inventory.json")
expect(ratchet?.env?.BASE_SHA).toBe("${{ github.event.pull_request.base.sha || github.event.before }}")
expect(ratchet?.env?.HEAD_SHA).toBe("${{ github.sha }}")
expect(ratchet?.run).toBe("node script/frontend-inventory.mjs --check-baseline --base \"$BASE_SHA\" --head \"$HEAD_SHA\"")
expect(checkNeeds).toContain(frontendArchitectureJobName)
})

test("splits required Linux unit jobs by package while preserving Turbo dependency semantics", () => {
const parsed = parseWorkflow(ciWorkflowPath)

Expand Down Expand Up @@ -432,7 +466,15 @@ describe("ci workflow", () => {
const validate = stepByName("check", "Validate CI result")

expect(check?.if).toBe("always()")
expect(needs).toEqual(["changes", "typecheck", "unit-ui-focused", "unit-app", "unit-opencode", "unit-desktop"])
expect(needs).toEqual([
"changes",
"typecheck",
frontendArchitectureJobName,
"unit-ui-focused",
"unit-app",
"unit-opencode",
"unit-desktop",
])
expect(needs).not.toContain(lintJobName)
expect(needs).not.toContain("unit-windows")
expect(needs).not.toContain("unit-windows-app")
Expand All @@ -443,11 +485,13 @@ describe("ci workflow", () => {
expect(needs).not.toContain("unit-windows-opencode-server-tools")
expect(validate?.env?.DOCS_ONLY).toBe("${{ needs.changes.outputs.docs_only }}")
expect(validate?.env?.TYPECHECK_RESULT).toBe("${{ needs.typecheck.result }}")
expect(validate?.env?.FRONTEND_ARCHITECTURE_RESULT).toBe("${{ needs['frontend-architecture'].result }}")
expect(validate?.env?.UNIT_UI_FOCUSED_RESULT).toBe("${{ needs['unit-ui-focused'].result }}")
expect(validate?.env?.UNIT_APP_RESULT).toBe("${{ needs['unit-app'].result }}")
expect(validate?.env?.UNIT_OPENCODE_RESULT).toBe("${{ needs['unit-opencode'].result }}")
expect(validate?.env?.UNIT_DESKTOP_RESULT).toBe("${{ needs['unit-desktop'].result }}")
expect(validate?.run).toContain("Docs-only change, daily CI skipped.")
expect(validate?.run).toContain("FRONTEND_ARCHITECTURE_RESULT")
expect(validate?.run).toContain("UNIT_UI_FOCUSED_RESULT")
expect(validate?.run).toContain("UNIT_APP_RESULT")
expect(validate?.run).toContain("UNIT_OPENCODE_RESULT")
Expand Down
51 changes: 51 additions & 0 deletions packages/opencode/test/github/frontend-inventory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { describe, expect, test } from "bun:test"
import { execFileSync, spawnSync } from "node:child_process"
import { cpSync, mkdirSync, writeFileSync } from "node:fs"
import path from "node:path"
import { tmpdir } from "../fixture/fixture"

const repoRoot = path.join(import.meta.dir, "../../../..")
const inventoryScript = path.join(repoRoot, "script", "frontend-inventory.mjs")

function git(cwd: string, args: string[]) {
return execFileSync("git", args, { cwd, encoding: "utf8" })
}

function writeLines(filePath: string, count: number) {
writeFileSync(
filePath,
Array.from({ length: count }, (_, index) => `export function value${index}() { return ${index} }`).join("\n") + "\n",
)
}

describe("frontend inventory", () => {
test("reports touched oversized production frontend files as warn-only baseline output", async () => {
await using tmp = await tmpdir({ git: true })
const workspace = tmp.path
cpSync(inventoryScript, path.join(workspace, "frontend-inventory.mjs"))
mkdirSync(path.join(workspace, "packages", "app", "src"), { recursive: true })

const largeFile = path.join(workspace, "packages", "app", "src", "large-view.ts")
writeLines(largeFile, 205)
git(workspace, ["add", "."])
git(workspace, ["commit", "-m", "test: add baseline file"])
const base = git(workspace, ["rev-parse", "HEAD"]).trim()

writeLines(largeFile, 206)
git(workspace, ["add", "."])
git(workspace, ["commit", "-m", "test: modify large file"])
const head = git(workspace, ["rev-parse", "HEAD"]).trim()

const result = spawnSync("node", ["frontend-inventory.mjs", "--check-baseline", "--base", base, "--head", head], {
cwd: workspace,
encoding: "utf8",
})

expect(result.status).toBe(0)
expect(result.stdout).toContain("Frontend LOC ratchet warnings")
expect(result.stdout).toContain("warn-only")
expect(result.stderr).toContain("::warning")
expect(result.stderr).toContain("packages/app/src/large-view.ts")
expect(result.stderr).toContain(">200 LOC")
})
})
Loading
Loading