= (event) => {
+ const next = resizeWidthFromKey(widthRef.current, event.key, event.shiftKey, boundsRef.current);
+ if (next === null) return;
+ event.preventDefault();
+ applyWidth(next, true);
+ };
+
+ return {
+ containerRef,
+ panelWidth,
+ resizing,
+ separatorProps: {
+ role: "separator",
+ tabIndex: 0,
+ "aria-label": "Resize model activity",
+ "aria-orientation": "vertical",
+ "aria-valuemin": bounds.min,
+ "aria-valuemax": bounds.max,
+ "aria-valuenow": panelWidth,
+ onPointerDown,
+ onPointerMove,
+ onPointerUp,
+ onPointerCancel,
+ onKeyDown,
+ },
+ };
+}
+```
+
+- [ ] **Step 5: Integrate the hook, responsive drawer, and zero-width hit-area wrapper**
+
+In `AppShell.tsx`, import `CSSProperties` and `useActivityPanelResize`, initialize:
+
+```tsx
+const { containerRef, panelWidth, resizing, separatorProps } = useActivityPanelResize();
+const activityWidthStyle = {
+ "--activity-panel-width": `${panelWidth}px`,
+} as CSSProperties;
+```
+
+Attach `ref={containerRef}`, `style={activityWidthStyle}`, and `data-activity-resizing={resizing}` to `app-shell`. Add `select-none cursor-col-resize` only while resizing. Between `ModelActivityPanel` and the conversation column, render:
+
+```tsx
+{showActivity && (
+
+)}
+```
+
+Set `data-dragging={resizing}` on the separator div. Change the conversation wrapper to:
+
+```tsx
+
+```
+
+In `ModelActivityPanel.tsx`, replace `w-2/5` with:
+
+```tsx
+className="fixed inset-y-0 left-0 z-30 flex w-[min(90vw,24rem)] shrink-0 flex-col border-r border-border bg-sidebar lg:static lg:z-auto lg:w-[var(--activity-panel-width)] lg:border-r-0"
+```
+
+- [ ] **Step 6: Run focused resize, panel, and shell tests**
+
+Run:
+
+```bash
+npx vitest run src/shell/useActivityPanelResize.test.ts src/shell/ModelActivityPanel.test.tsx src/shell/AppShell.session-mgmt.test.tsx
+npx tsc -b
+```
+
+Expected: all focused tests and TypeScript pass; the panel restores saved widths, pointer and keyboard changes clamp/persist, and no fixed 40/60 class remains.
+
+- [ ] **Step 7: Review and commit Task 2**
+
+Run:
+
+```bash
+cd ..
+git diff --check
+git diff -- frontend/src/shell/useActivityPanelResize.ts frontend/src/shell/useActivityPanelResize.test.ts frontend/src/shell/ModelActivityPanel.tsx frontend/src/shell/AppShell.tsx frontend/src/shell/AppShell.session-mgmt.test.tsx
+git add frontend/src/shell/useActivityPanelResize.ts frontend/src/shell/useActivityPanelResize.test.ts frontend/src/shell/ModelActivityPanel.tsx frontend/src/shell/AppShell.tsx frontend/src/shell/AppShell.session-mgmt.test.tsx
+git commit -m "feat(frontend): add resizable activity split"
+```
+
+Expected: the commit contains only the split helper, responsive shell integration, panel layout class, and tests.
+
+---
+
+### Task 3: Reduce the true vertical height sources in F6 CTE cards
+
+**Files:**
+- Modify: `frontend/src/viewers/CtePlanViewer.tsx`
+- Test: `frontend/src/viewers/CtePlanViewer.test.tsx`
+
+**Interfaces:**
+- Consumes: unchanged `CtePlanV2`, `CtePlanCte`, `CtePlanFilter`, `Card`, `CardHeader`, and `CardContent`.
+- Produces: the same semantic CTE plan with compact internal rhythm and unchanged horizontal padding/grid topology.
+
+- [ ] **Step 1: Replace padding-only assertions with failing complete density assertions**
+
+Update the compact-spacing tests in `CtePlanViewer.test.tsx` to assert:
+
+```tsx
+test("uses compact CTE section rhythm with moderate lateral padding", () => {
+ const { container } = render();
+ for (const header of container.querySelectorAll('[data-slot="card-header"]')) {
+ expect(header).toHaveClass("gap-2", "px-3", "py-2", "sm:px-4", "[&.border-b]:pb-2");
+ expect(header).not.toHaveClass("gap-3", "p-4", "sm:p-5");
+ }
+ for (const content of container.querySelectorAll('[data-slot="card-content"]')) {
+ expect(content).toHaveClass("gap-3", "px-3", "py-2", "sm:px-4");
+ expect(content).not.toHaveClass("gap-5", "p-4", "sm:p-5");
+ }
+});
+
+test("compacts table, filter, detail, and rationale rows", () => {
+ render();
+ const card = getCteCard("pazienti_idonei");
+ const tableRow = within(card).getByText("pazienti").parentElement;
+ expect(tableRow).toHaveClass("px-3", "py-1", "sm:px-4");
+
+ const filter = within(card).getByRole("group", { name: "Filter 1" });
+ expect(filter).toHaveClass("space-y-2", "px-3", "py-1", "sm:px-4");
+ expect(filter.firstElementChild).toHaveClass("gap-2");
+ const filterDetails = within(card).getByText("Solo pazienti in follow-up").parentElement;
+ expect(filterDetails).toHaveClass("gap-y-1", "pt-1");
+
+ const rationale = within(card)
+ .getByText("Base della catena: riduce il volume prima dei join")
+ .closest("section");
+ expect(rationale).toHaveClass("pt-1");
+ expect(within(rationale!).getByText("Rationale")).toHaveClass("mb-1");
+});
+```
+
+Retain the existing semantic, optional-section, ordered-list, filter alignment, and long-wrap tests. Change existing `py-2`/`pt-2` assertions for table/filter/detail/rationale rows to `py-1`/`pt-1`.
+
+- [ ] **Step 2: Run the viewer tests and verify RED**
+
+Run:
+
+```bash
+cd frontend
+npx vitest run src/viewers/CtePlanViewer.test.tsx
+```
+
+Expected: exact class failures identify `gap-5`, `gap-3`, `py-2`, `space-y-3`, and `pt-2` as the remaining excessive height sources.
+
+- [ ] **Step 3: Apply the approved compact class map**
+
+In `CtePlanViewer.tsx`, make these exact replacements:
+
+```text
+FilterRow root: space-y-3 px-3 py-2 -> space-y-2 px-3 py-1
+Filter field grid: gap-3 -> gap-2
+Filter code/text: leading-relaxed -> leading-4
+Filter details: gap-y-2 pt-2 -> gap-y-1 pt-1
+CardHeader: gap-3 -> gap-2
+Purpose/name lines: leading-relaxed -> leading-5
+CardContent: gap-5 -> gap-3
+Depends/keys grid: gap-4 -> gap-3
+Section headings: mb-2 -> mb-1
+Table rows: py-2 -> py-1
+Table code/text: leading-relaxed -> leading-4
+CTE rationale: pt-2 -> pt-1
+Rationale heading: mb-1.5 -> mb-1
+Rationale text: leading-relaxed -> leading-5
+```
+
+Keep CardHeader/CardContent outer `py-2`, all `px-3 sm:px-4`, chips, badges, borders, `gap-3` between distinct CTE cards, grids, section conditions, and wrapping classes unchanged.
+
+- [ ] **Step 4: Run focused tests, TypeScript, and the layout detector**
+
+Run:
+
+```bash
+npx vitest run src/viewers/CtePlanViewer.test.tsx
+npx tsc -b
+node /home/admlocforn1/.codex/skills/impeccable/scripts/detect.mjs --json --scope layout \
+ src/viewers/CtePlanViewer.tsx
+```
+
+Expected: viewer tests and TypeScript pass; detector output is exactly `[]`.
+
+- [ ] **Step 5: Review and commit Task 3**
+
+Run:
+
+```bash
+cd ..
+git diff --check
+git diff -- frontend/src/viewers/CtePlanViewer.tsx frontend/src/viewers/CtePlanViewer.test.tsx
+git add frontend/src/viewers/CtePlanViewer.tsx frontend/src/viewers/CtePlanViewer.test.tsx
+git commit -m "style(frontend): tighten CTE plan vertical rhythm"
+```
+
+Expected: only the CTE viewer and test are committed; shared `card.tsx` remains untouched.
+
+---
+
+### Task 4: Full verification, frontend-only Docker deployment, and durable state
+
+**Files:**
+- Modify: `brain/codebase/workflow-ui-contracts.md`
+- Modify: `PROJECT_STATE.md`
+
+**Interfaces:**
+- Consumes: Tasks 1–3, the existing root Compose `frontend` service, and the portal Vite manifest cache.
+- Produces: verified source, a running updated frontend image, unchanged core runtime, and durable UI/deployment evidence.
+
+- [ ] **Step 1: Run the complete source verification gate**
+
+Run:
+
+```bash
+cd frontend
+npx vitest run
+npx tsc -b
+npm run build
+node /home/admlocforn1/.codex/skills/impeccable/scripts/detect.mjs --json --scope layout \
+ src/shell/AppShell.tsx src/shell/ModelActivityPanel.tsx \
+ src/shell/CentralStatus.tsx src/viewers/CtePlanViewer.tsx
+cd ..
+git diff --check
+git status --short --untracked-files=all
+```
+
+Expected: every frontend test passes, TypeScript/build exit 0, detector output is `[]`, and `.vite/` remains untracked. Record exact test/file counts and the emitted Vite entry for `PROJECT_STATE.md`.
+
+- [ ] **Step 2: Record live runtime state without mutating sessions or configuration**
+
+Run:
+
+```bash
+docker inspect -f '{{.Name}}|{{.State.Status}}|{{if .State.Health}}{{.State.Health.Status}}{{else}}no-healthcheck{{end}}|{{.Image}}|{{.State.StartedAt}}' \
+ thothii-core-1 thothii-frontend-1 omics_portal-web-1
+docker exec thothii-frontend-1 sh -lc \
+ "rg -n -B 3 '\"isEntry\": true' /usr/share/nginx/html/.vite/manifest.json 2>/dev/null || grep -n -B 3 '\"isEntry\": true' /usr/share/nginx/html/.vite/manifest.json"
+docker top thothii-core-1 -eo pid,ppid,etime,args
+```
+
+Expected: capture core/frontend/portal image and start timestamps, current Vite entry, and whether an unrelated Pi process exists. Do not stop it, close a session, read settings, print session questions, or run a model smoke.
+
+- [ ] **Step 3: Build and recreate only the frontend service**
+
+If execution occurs in an isolated worktree, create only the ignored deployment-environment symlink when absent, without reading or copying the target. Then run from the repository root:
+
+```bash
+if [ ! -e deploy/thothii.env ]; then
+ ln -s /home/chirone/ThothII/deploy/thothii.env deploy/thothii.env
+fi
+test -e deploy/thothii.env
+docker compose build frontend
+docker compose up -d --no-deps --force-recreate --wait --wait-timeout 60 frontend
+docker inspect -f '{{.Name}}|{{.State.Status}}|{{.Image}}|{{.State.StartedAt}}' thothii-frontend-1
+docker exec thothii-frontend-1 sh -lc \
+ "rg -n -B 3 '\"isEntry\": true' /usr/share/nginx/html/.vite/manifest.json 2>/dev/null || grep -n -B 3 '\"isEntry\": true' /usr/share/nginx/html/.vite/manifest.json"
+```
+
+Expected: build and frontend-only recreation succeed; record the new image and entry. If and only if the Vite entry differs from Step 2, run exactly:
+
+```bash
+docker restart omics_portal-web-1
+docker inspect -f '{{.Name}}|{{.State.Status}}|{{.State.StartedAt}}' omics_portal-web-1
+```
+
+Do not recreate/restart core or any other portal service.
+
+- [ ] **Step 4: Verify deployment isolation and count-only logs**
+
+Run:
+
+```bash
+docker inspect -f '{{.Name}}|{{.State.Status}}|{{if .State.Health}}{{.State.Health.Status}}{{else}}no-healthcheck{{end}}|{{.Image}}|{{.State.StartedAt}}' \
+ thothii-core-1 thothii-frontend-1 omics_portal-web-1
+docker top thothii-core-1 -eo pid,ppid,etime,args
+frontend_log_patterns=$(docker logs --since 10m --tail 200 thothii-frontend-1 2>&1 | \
+ rg -i -c 'fatal|emerg|panic|authorization:|api[_-]?key|password|secret' || true)
+printf 'frontend_log_patterns=%s\n' "${frontend_log_patterns:-0}"
+```
+
+Expected: core remains healthy with exactly its Step 2 image/start timestamp, frontend and portal run normally, an unrelated Pi process remains untouched, and the count-only scan reports 0. Never print raw application logs or secret values.
+
+- [ ] **Step 5: Update durable UI contracts and project state**
+
+Replace the two obsolete activity/CTE bullets in `brain/codebase/workflow-ui-contracts.md` with:
+
+```markdown
+- `activityLog` remains the complete in-memory chronological fold. The left Model activity panel
+ default-denies every kind except prompt, thinking, and assistant, presenting them as Question,
+ Reasoning, and Response in source order; status, tool, gate, lifecycle, and unknown kinds stay
+ hidden. Its desktop width is pointer/keyboard resizable from 288–576 px while preserving 512 px
+ centrally, persists globally in localStorage, and becomes an overlay drawer below `lg`.
+- F6 CTE cards keep their semantic structure, responsive grids, 12/16 px lateral padding, and 8 px
+ header/content edge padding. Internal section gaps are 12 px, headings/dividers use 4 px spacing,
+ and table/filter rows use 4 px vertical padding with compact line heights.
+```
+
+Add a dated resolved-state section to `PROJECT_STATE.md` containing source HEAD, exact frontend test count, TypeScript/build/detector results, new frontend image ID, old/new Vite entries, portal restart decision, unchanged core image/start timestamp, final container state, and preservation of any unrelated Pi process. Explicitly state that no live model smoke was run.
+
+- [ ] **Step 6: Review documentation scope and commit evidence**
+
+Run:
+
+```bash
+git diff --check
+git status --short --untracked-files=all
+git diff -- brain/codebase/workflow-ui-contracts.md PROJECT_STATE.md
+git add brain/codebase/workflow-ui-contracts.md PROJECT_STATE.md
+git diff --cached --check
+git commit -m "docs: record activity split deployment"
+```
+
+Expected: only the two documentation files enter the commit; `.vite/`, runtime configuration, settings, and session data remain unstaged.
+
+---
+
+## Final review gate
+
+Before claiming completion, use `requesting-code-review` on the complete implementation range, resolve every Critical/Important finding, then use `verification-before-completion` to rerun the relevant focused tests, the complete frontend suite, TypeScript, build, detector, `git diff --check`, and runtime/container evidence. The final handoff must report the exact frontend image, Vite entry, test count, portal restart decision, and unchanged core start timestamp.