Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
138e3b8
feat(workbench): add governed work design foundation
Aug 13, 2026
5b3b46e
feat(workbench): add scoped token rotation issuer
Aug 13, 2026
6b0fa29
feat(workbench): add typed client and event dispatcher
Aug 13, 2026
2ed127f
feat(workbench): add executable route registry
Aug 13, 2026
916f8b6
feat(workbench): add resumable operations and cancellation
Aug 13, 2026
857bd67
feat(workbench): add bounded workspace file index
Aug 13, 2026
5bca856
feat(workbench): add approval and audit observability
Aug 14, 2026
e3bdc10
feat(workbench): expose artifact lineage reads
Aug 14, 2026
246abf7
feat(workbench): add artifact lineage writes
Aug 14, 2026
7afe39f
feat(workbench): gate local artifact exports
Aug 14, 2026
45ea951
fix(ci): satisfy SPDX conformance gate
Aug 14, 2026
070c23c
feat(workbench): validate workspace design specs
Aug 14, 2026
9d00f34
feat(workbench): expose typed file search client
Aug 14, 2026
aede7fc
feat(workbench): expose artifact and document catalogs
Aug 14, 2026
ccf5d51
feat(workbench): expose typed trace and activity pages
Aug 14, 2026
c394880
feat(workbench): add approval capability and export client flows
Aug 14, 2026
d1e1e5c
feat(workbench): add design spec panel model
Aug 14, 2026
40ca96e
feat(workbench): add deterministic design renderer
Aug 14, 2026
e7155a6
feat(workbench): add responsive design previews
Aug 14, 2026
ce0d1d4
feat(workbench): add design file catalog adapter
Aug 14, 2026
583296a
feat(workbench): render design file rows
Aug 14, 2026
be8256b
feat(workbench): add design system catalog picker
Aug 14, 2026
96def2c
feat(workbench): add artifact version panel model
Aug 14, 2026
fbc8e06
feat(workbench): add shared mobile navigation model
Aug 14, 2026
57fd6f4
docs(workbench): record Work Design release candidate
Aug 14, 2026
439486f
docs(workbench): record full conformance evidence
Aug 14, 2026
2dcc356
docs(workbench): record partial Android runtime evidence
Aug 14, 2026
a518ef2
docs(workbench): record Android lifecycle permission boundary
Aug 14, 2026
54abaa8
docs(workbench): reconcile verification state
Aug 14, 2026
d981114
docs(workbench): record green conformance run
Aug 14, 2026
0f0d482
feat(app): wire Work Design surfaces
Aug 14, 2026
8035c40
docs(workbench): record app bundle evidence
Aug 14, 2026
a04be04
feat(app): make Work Design surfaces interactive
Aug 14, 2026
6aa9035
docs(workbench): refresh final checkpoint SHA
Aug 14, 2026
bd6b480
docs(workbench): map blocked gate unlock paths
Aug 14, 2026
4e18128
feat(workbench): implement versioned protocol handshake
Aug 14, 2026
81dff38
docs(workbench): record handshake progress
Aug 14, 2026
efbca76
feat(workbench): serialize client token rotation
Aug 14, 2026
7d129cf
docs(workbench): record token rotation progress
Aug 14, 2026
6e8e676
feat(workbench): inject native scoped token authority
Aug 14, 2026
1f15656
docs(workbench): record native issuer boundary
Aug 14, 2026
2e1cae6
feat(workbench): add native token bridge adapter
Aug 14, 2026
9270333
docs(workbench): fix execution state checkpoint
Aug 14, 2026
5c5d64d
test(workbench): cover native bridge in shell suite
Aug 14, 2026
28b7827
test(workbench): prove fresh instance identity on restart
Aug 14, 2026
bc19626
docs(workbench): fix restart checkpoint
Aug 14, 2026
7ca8d66
fix(workbench): consume native scoped tokens in server boundary
Aug 14, 2026
4705655
feat(workbench): add live native connection controller
Aug 14, 2026
44f700b
feat(workbench): inject live connection into app surface
Aug 14, 2026
18acf36
test(workbench): prove concurrent automatic port ownership
Aug 14, 2026
c51218e
test(security): parse packaged CSP directives
Aug 14, 2026
e39fec3
fix(workbench): validate native rotation lease payloads
Aug 14, 2026
e2a82c9
refactor(workbench): keep native lease metadata opaque
Aug 14, 2026
9a60241
docs(workbench): reconcile implementation checkpoint state
Aug 14, 2026
058329c
test(workbench): prove cross-process topology isolation
Aug 14, 2026
3d7cc94
feat(workbench): wire native desktop and mobile bridges
Aug 14, 2026
55d40d7
feat(desktop): add Workbench platform adapter
Aug 14, 2026
b84739f
test(workbench): cover native token rotation lifecycle
Aug 14, 2026
6164dea
docs(work-design): record native lifecycle proof
Aug 14, 2026
e549815
test(conformance): include native workbench bridge
Aug 14, 2026
7d3ec8c
docs(work-design): record full conformance proof
Aug 14, 2026
95d948e
docs(work-design): record green CI rerun
Aug 14, 2026
80be385
feat(workbench): make workspace manifest the design authority
Aug 14, 2026
619d9a4
docs(work-design): close design system authority gate
Aug 14, 2026
745aa86
docs(work-design): record green G6 CI
Aug 14, 2026
91d2220
fix(app): wire work design and automate modes
Aug 14, 2026
f10c1cc
fix(app): guard mode navigation without workspace
Aug 14, 2026
a268a6e
feat(multimode): complete workbench mode routing and e2e coverage
Aug 14, 2026
f14c686
fix(multimode): preserve session identity across mode routes
Aug 14, 2026
e709169
fix(multimode): close production readiness findings
Aug 14, 2026
a31a3b8
chore(desktop): update electron security baseline
Aug 14, 2026
6863267
fix(security): harden web and desktop dependency baseline
Aug 14, 2026
df3a35d
test(workbench): close multimode coverage gaps
Aug 14, 2026
449ad7e
fix(mobile): enable reqwest json requests
Aug 14, 2026
55641b0
ci: update CodeQL actions
Aug 14, 2026
b7add2b
fix(app): add chat entry points to workbench modes
Aug 15, 2026
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
8 changes: 4 additions & 4 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
queries: security-extended,security-and-quality

- name: Autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@v4

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{ matrix.language }}"
36 changes: 36 additions & 0 deletions .github/workflows/work-design-integrity.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: work-design-integrity

on:
pull_request:
branches: [dev]
paths:
- "work_design/**"
- "INTEGRATION.md"
- "scripts/check-pr-size.sh"
- ".github/workflows/work-design-integrity.yml"

permissions:
contents: read

jobs:
merge-and-size:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Fetch base
run: git fetch origin dev --depth=1

- name: Check mergeability without mutating the checkout
run: git merge-tree --write-tree origin/dev HEAD

- name: Check card size
run: bash scripts/check-pr-size.sh dev

- name: Check whitespace
run: git diff --check origin/dev...HEAD

- name: Check Workbench security policy
run: node scripts/check-workbench-security.mjs
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,4 @@ unifia-conformance.json
# repository path and per-machine tool versions, so it is a local record rather
# than something to track.
artifacts/
packages/mobile/src-tauri/ort-runtime/
27 changes: 27 additions & 0 deletions INTEGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Work/Design integration ownership

This file records high-conflict surfaces and the rule for resolving overlap between cards.

## Ownership map

| Surface | Owner card | Conflict rule |
|---|---|---|
| `packages/app/src/pages/layout.tsx` | M4/M16 | Keep route registration and shell composition in one card; split any conflict before resolving. |
| `packages/app/src/app.tsx` | M4 | Preserve the existing provider order; route/context changes require an architecture note. |
| `packages/app/package.json` | M2a | Dependency and export changes are atomic with the lockfile generated by the package manager. |
| `packages/workbench-server/src` | M1b/M1c/M6-M10 | Server transport, security, and feature routes remain separate card boundaries. |
| Tauri configuration files | M1c/M22 | Native bridge and packaging changes require platform evidence before integration. |
| `packages/contracts` | M3 | Wire contracts have one owner; consumers do not duplicate schemas. |

## Resolution budget

- Resolve at most 50 changed lines in a card-level conflict.
- Above that limit, stop, record the conflict, and split the card before editing.
- Never resolve by accepting an entire side blindly.

## File-type rules

- TypeScript: preserve the local formatter and package boundary.
- JSON/YAML: keep generated ordering; regenerate rather than hand-edit generated output.
- Native configuration: preserve platform identifiers unless a reviewed gate explicitly changes them.
- Tests: add evidence next to the behavior they prove; do not weaken an existing assertion to make a card pass.
723 changes: 723 additions & 0 deletions PLAN-UNIFIED-MULTIMODE-WORKBENCH-CORRECTIVE-2026-08-14.md

Large diffs are not rendered by default.

480 changes: 304 additions & 176 deletions bun.lock

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions docs/adr/0010-shell-modes-project-workbench-workspace.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!-- SPDX-License-Identifier: MIT -->

# ADR-0010: Shell modes project a single Workbench workspace

**Date**: 2026-08-14 | **Status**: Accepted

## Context

Code, Work, Design and Automate must preserve one decoded workspace, one session lineage and one native Workbench lease. Route state, mode chrome and resource data previously had independent fallback paths, which made a return to Code diverge from the visible projection.

## Decision

The URL is the authoritative mode selector. `DirectoryLayout` owns the decoded workspace boundary and mounts one `WorkspaceWorkbenchProvider`; Workbench lifecycle state owns connection, rollback, deadline and cleanup. Surfaces consume TanStack Query entries keyed by server origin, instance, workspace, resource and parameters. SessionHeader titlebar content is registered through reactive refs owned by the Solid titlebar.

## Alternatives rejected

- A connection per mode: rejected because workspace-scoped revoke can invalidate a sibling mode.
- DOM id lookup for titlebar slots: rejected because it races mount order and does not react to replacement.
- Silent fallback to Code or demo data: rejected because invalid state must remain visible and diagnosable.

## Consequences

Mode changes are route transitions over shared providers, not runtime launches. A native bearer remains short-lived and scoped; signing material and IPC secrets stay native. GUI smoke tests remain required for Windows controls, DPI, shutdown and Snap behavior.
51 changes: 51 additions & 0 deletions docs/adr/0033-automate-v0-read-only-surface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!-- SPDX-License-Identifier: MIT -->

---
id: 0033
title: Automate v0 read-only workspace surface
status: ACCEPTED
date: 2026-08-14
---

# ADR-0033: Automate v0 read-only workspace surface

## Context

The desktop Automate route needs a real workspace-backed surface, but the
current Workbench server exposes workflow execution only through POST actions.
It has no typed read-only endpoint for listing workflow definitions. A static
placeholder or an invented API would make unavailable functionality look real.

## Decision

Automate v0 reads the bounded `.unifia/workflows` directory through the existing
`WorkbenchClient.listFiles()` contract with `workspace.read`. It displays the
real definition paths, an explicit empty state, connection state, and errors.

Workflow execution, scheduling, and approval actions remain unavailable until a
versioned read/write workflow contract is added to the authoritative server and
client packages. The UI must not expose an active execution button before that
contract exists.

## Alternatives rejected

- Inventing `GET /v1/workflows`: rejected because no server contract exists.
- Calling the existing workflow POST route from the UI: rejected because it
would require an untyped definition and an execution capability.
- Keeping a static Automate placeholder: rejected because it provides no real
workspace behavior and hides the implementation boundary.

## Consequences

- Automate is a genuine read-only vertical slice in v0.
- The workspace remains the authority for available definitions.
- No workflow execution is claimed or simulated.
- A future workflow catalog endpoint can replace the file-index adapter without
changing the mode navigation contract.

## Implementation references

- `packages/app/src/pages/workbench-mode.tsx`
- `packages/workbench-shell/src/client.ts`
- `packages/workbench-server/src/index.ts`
- `packages/workflow-runtime/src/index.ts`
59 changes: 59 additions & 0 deletions docs/autonomy/WORK-DESIGN-UI-REPAIR-2026-08-14.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!-- SPDX-License-Identifier: MIT -->

---
project: unifia
type: roadmap
tags: [work-design, desktop, tauri, modes, bridge]
summary: "Diagnostic et correction de la navigation UI Work/Design/Automate sans fallback ni contrat Workbench inventé."
created: 2026-08-14
updated: 2026-08-14
related: [[work_design/DECISIONS|Work/Design decisions]], [[work_design/MANUAL-VERIFICATION|Manual verification]], [[docs/adr/0033-automate-v0-read-only-surface|Automate v0 ADR]]
---

# Work/Design UI repair — 2026-08-14

## Baseline

- Worktree : `D:\App\OpenCode\opencode-work-design`
- Branche : `work-design`
- HEAD initial : `745aa86b82`
- Merge-base `work-design`/`dev` : `91daa35a26a8e44d7f35b539c91030ec1e230c54`
- Dirty state initial : `mode.test.ts`, `mode.tsx`, `mode-directory.ts` uniquement
- Références : `opencode-unifia-rebrand` est une snapshot sans dépôt Git exploitable ; `unifia-execution-clean` est sur `a37f5115d`.

## P0 conclusions

- Les contrats headless `workbench-shell` (`modes.ts`, `shell.ts`, `shell.test.ts`) sont byte-identiques dans les trois arbres comparables.
- Le dépôt actif possède des contrats supplémentaires déjà implémentés : client typed, routes, manifest G6, serveur, artefacts, Design System et opérations.
- `ModeProvider` était global dans `app.tsx` mais dépendait de `useParams()` alors que `:dir` est fourni par `DirectoryLayout`. La résolution a été déplacée vers le premier segment de `useLocation().pathname`, puis décodée une seule fois.
- Le changement de mode conserve désormais l’override `?session=`.
- Le clic UI direct n’a pas été automatisé dans cette session ; il reste `MANUAL_VERIFICATION_REQUIRED` dans MV-06.

## Changements réalisés

- `packages/app/src/context/mode-directory.ts` : extraction du segment route, décodage et conservation contrôlée de `session`.
- `packages/app/src/context/mode.tsx` : suppression de la dépendance à `useParams()` hors route.
- `packages/app/src/context/mode.test.ts` : régressions route/workspace/session.
- `packages/app/src/pages/workbench-mode.tsx` : état bridge borné avec erreur/retry, données réelles Work (`documents`, `artifacts`, `files`), export réel du premier artefact, manifest/catalogues Design réels, et Automate v0 en lecture seule de `.unifia/workflows`.
- `ModeProvider` owns one workspace-scoped Workbench connection consumed by Work, Design and Automate; switching surfaces no longer creates three independent connections.
- `docs/adr/0033-automate-v0-read-only-surface.md` : contrat Automate v0 sans API workflow inventée.

## Evidence

- App typecheck : PASS.
- App suite : 707 tests PASS.
- Workbench shell canonical script : PASS (`122/122`, client `27/27`, bridge `5/5`, connection `2/2`, routes `11/11`, DesignSystem `6/6`).
- Workbench server canonical script : PASS (handshake `5/5`, server `72/72`, bootstrap `40/40`, topology `5/5`, Vitest `4/4`).
- App production build : PASS.
- Tauri `bun run tauri build --no-bundle` : PASS, `Unifia.exe` produit le 2026-08-14 à 17:07:55.
- Hash binaire : `3C4EE538DA6DCF35E79C41574EB81016176506485ED95F99655208C974BFBC25`.
- Processus fraîchement lancé : `Unifia` répondant et `unifia-cli` répondant.

## Restant ouvert

- MV-01/MV-02 : traces bridge natif, rotation et révocation.
- MV-06 : clic réel Code/Work/Design/Automate, deep links, deux workspaces et réouverture.
- MV-07/MV-08 : cycle de vie desktop et single-writer cross-process.
- MV-09 : CSP extraite du bundle et essais interactifs URL.
- L’export Work est branché sur le contrat réel, mais doit être exercé avec un workspace contenant un artefact et l’approbation/capability attendue.
- Automate ne permet volontairement aucune exécution tant qu’un contrat typed de catalogue/exécution workflow n’est pas ajouté au serveur.
17 changes: 11 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@
"solid-list": "0.3.0",
"tailwindcss": "4.1.11",
"virtua": "0.42.3",
"vite": "7.1.4",
"vite": "7.3.5",
"@solidjs/meta": "0.29.4",
"@solidjs/router": "0.15.4",
"solid-js": "1.9.10",
"@solidjs/router": "0.15.4",
"solid-js": "1.9.10",
"vite-plugin-solid": "2.11.10"
}
},
Expand All @@ -99,7 +99,8 @@
"@unifia/plugin": "workspace:*",
"@unifia/script": "workspace:*",
"@unifia/sdk": "workspace:*",
"typescript": "catalog:"
"typescript": "catalog:",
"wrangler": "4.123.0"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -132,8 +133,12 @@
"uuid": "13.0.1",
"defu": "6.1.7",
"seroval": "1.5.4",
"react-router": "6.30.2",
"wrangler": "4.89.1",
"react-router": "6.30.4",
"astro": "7.2.2",
"vite": "7.3.5",
"js-yaml": "4.3.1",
"nanoid": "3.3.18",
"wrangler": "4.123.0",
"ip-address": "10.5.0",
"dompurify": "3.4.13",
"fast-xml-parser": "5.7.3",
Expand Down
1 change: 1 addition & 0 deletions packages/app/e2e/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export async function startBackend(label: string, input?: { llmUrl?: string }):
const url = `http://127.0.0.1:${port}`
try {
await waitForHealth(url)
await waitForHealth(url, "/config/providers")
} catch (error) {
proc.kill("SIGTERM")
await fs.rm(sandbox, { recursive: true, force: true }).catch(() => undefined)
Expand Down
52 changes: 52 additions & 0 deletions packages/app/e2e/modes/mode-navigation.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/* SPDX-License-Identifier: MIT */

import { test, expect } from "../fixtures"
import { dirPath } from "../utils"

test("multimode navigation keeps the route and projection aligned", async ({ page, directory, slug }) => {
await page.setViewportSize({ width: 1400, height: 800 })
await page.goto(`${dirPath(directory)}/session`)
await expect(page).toHaveURL(new RegExp(`/${slug}/session(?:[/?#]|$)`))

const modes = ["work", "design", "automate"] as const
for (const mode of modes) {
await page.getByRole("button", { name: `${mode} mode` }).click()
await expect(page).toHaveURL(new RegExp(`/${slug}/${mode}(?:[/?#]|$)`))
await expect(page.locator(`[data-workbench-mode="${mode}"]`).first()).toBeVisible()
await expect(page.locator(`[data-workbench-chat="${mode}"]`)).toBeVisible()
await expect(page.locator("[data-workbench-chat-input]")).toBeVisible()
await page.locator("[data-workbench-chat-suggestion]").click()
await expect(page.locator("[data-workbench-chat-input]")).not.toHaveValue("")
}

await page.getByRole("button", { name: "code mode" }).click()
await expect(page).toHaveURL(new RegExp(`/${slug}/session(?:[/?#]|$)`))
await expect(page.locator(`[data-workbench-mode="code"]`).first()).toBeVisible()
})

test("unknown mode never renders an empty projection", async ({ page, directory }) => {
await page.goto(`${dirPath(directory)}/unknown-mode`)
await expect(page.locator("[data-workbench-error], [data-workbench-mode=code]").first()).toBeVisible()
})

test("workbench surfaces fail closed before a native bridge is available", async ({ page, directory }) => {
await page.setViewportSize({ width: 1400, height: 800 })
await page.goto(`${dirPath(directory)}/session`)

await page.getByRole("button", { name: "work mode" }).click()
await expect(page.locator('[data-workbench-surface="work"]')).toBeVisible()
await expect(page.getByText("Chat remains available")).toBeVisible()
await expect(page.locator("[data-workbench-connection]")).toBeVisible()
await expect(page.locator("[data-workbench-operation]")).toHaveCount(11)
await page.locator('[data-workbench-operation="export"]').click()
await expect(page.locator("[data-workbench-export]")).toBeDisabled()

await page.getByRole("button", { name: "design mode" }).click()
await expect(page.locator('[data-workbench-surface="design"]')).toBeVisible()
await page.locator("#workbench-design-spec").fill('{"id":"broken"}')
await expect(page.locator("[data-workbench-diagnostics]")).toBeVisible()

await page.getByRole("button", { name: "automate mode" }).click()
await expect(page.locator('[data-workbench-surface="automate"]')).toBeVisible()
await expect(page.locator("[data-automate-connection]")).toBeVisible()
})
33 changes: 33 additions & 0 deletions packages/app/e2e/modes/mode-prompts.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: MIT */

import { test, expect } from "../fixtures"
import { assistantText, waitSessionIdle } from "../actions"
import { dirPath } from "../utils"

test("each workspace mode accepts a prompt on the active session", async ({ page, project, assistant }) => {
test.setTimeout(120_000)

await project.open()
const sessionID = await project.user("Create the temporary E2E session and do not modify files.")
const modes = ["code", "work", "design", "automate"] as const

for (const mode of modes) {
const token = `MODE_${mode.toUpperCase()}_${Date.now()}`
await assistant.reply(token)
const route = mode === "code" ? `${dirPath(project.directory)}/session/${sessionID}` : `${dirPath(project.directory)}/${mode}`
await page.goto(route)
await expect(page).toHaveURL(mode === "code" ? new RegExp(`/session/${sessionID}(?:[/?#]|$)`) : new RegExp(`/${mode}(?:[/?#]|$)`))
await expect(page.locator(`[data-workbench-mode="${mode}"]`).first()).toBeVisible()

const callsBefore = await assistant.calls()
await project.sdk.session.prompt({
sessionID,
agent: "build",
parts: [{ type: "text", text: `Reply with exactly: ${token}` }],
})

await expect.poll(() => assistant.calls(), { timeout: 30_000 }).toBeGreaterThan(callsBefore)
await waitSessionIdle(project.sdk, sessionID, 30_000)
await expect.poll(() => assistantText(project.sdk, sessionID), { timeout: 30_000 }).toContain(token)
}
})
1 change: 1 addition & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@kobalte/core": "catalog:",
"@unifia/sdk": "workspace:*",
"@unifia/sdk-shared": "workspace:*",
"@unifia/workbench-shell": "workspace:*",
"@unifia/ui": "workspace:*",
"@unifia/util": "workspace:*",
"@shikijs/transformers": "3.9.2",
Expand Down
Loading
Loading