= (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.