Sidebar brand row now shows the running build's DD/MM HH:MM next to
the Cial logo (sourced from the system.version event already plumbed
through useDeployStatus) and a PanelLeftClose button to collapse the
panel. When collapsed, AppShell renders a floating hamburger button
at the top-left that re-opens it — mirrors the Old Cial pattern so
the workspace can claim the full width when desired.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
`session.messages[i]` is typed as `Message | undefined` under
noUncheckedIndexedAccess. Optional-chain the role check so the
agent's lastUserIdx scan type-checks again — the runtime semantics
are identical (loop only runs while `i` is in range).
Adds a transient `turnStartedAt` to the Session schema (not persisted —
mixed in by the back from ProcessManager). chat.send now broadcasts
`session.updated` with `status='busy'` + `turnStartedAt` to every tab,
and process-bridge's finalize broadcasts the idle flip with
`turnStartedAt: null`. session.list and session.rename payloads are
decorated so the field is always coherent.
Front maps it onto `streamingStartedAt`, so the sidebar timer in
`SessionRow` now ticks for any in-flight session — across tabs, after
reload, and even when the user is viewing a different session.
Persists a streaming-state snapshot to chat_session.streaming_state
every ~500 ms during a turn (text + thinking + collected tools), and
returns it from session.history (first page only) along with the
session status and turnStartedAt.
Front hydrates streamingText/Thinking/pendingTools/streamingStartedAt
from that payload when the session is busy, so a page reload mid-turn
no longer drops the active notch and pending tool calls — matches the
Old Cial reload behavior.
Splits POST /self/deploy (build only, blocking) from POST /self/restart
(response-first when bouncing core-back) so the agent gets a clean
"build done" signal and can decide when to apply.
Adds system.deploying / system.deploy_done / system.restarting /
system.version WS events broadcast to all sockets, plus
GET /api/v1/system/version for post-restart confirmation.
UI: DeployBadge component renders building/restarting/reconnecting/
reloading/failed stages above the composer; Sidebar Cial logo gradient
mirrors the same lifecycle. AppShell shares one SessionsClient so the
deploy state machine and the session store consume the same socket.
BuildRunner snapshots .next/static/chunks to chunks-prev/ before next
build; edge falls back to chunks-prev (rewrite or direct disk read) so
already-open tabs survive the build window and hard-reload only after
a confirmed version mismatch on reconnect.
Skills (cial:build, cial:restart) updated for the new contract.
`next build` always emits a production bundle, so serving it with
NODE_ENV=development at runtime mixes dev React with prod-built output
and surfaces as `useContext: null` during prerender (most visibly on
the synthetic `/global-error` route). Old-Cial parity is the dev tenant
running fully in production mode — `next dev`-style hot reload was
already gone after the watcher-removal commit, so the env-var was the
only remaining mismatch.
- supervisor.dev.ts: all four service children now spawn with
NODE_ENV=production; rationale moved to the file's docblock so it
isn't repeated four times.
- dev-entrypoint.sh: `pnpm turbo run build` runs with NODE_ENV=production
too. The Dockerfile's image-level NODE_ENV=development is left intact
so `pnpm install` keeps installing devDependencies.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After a normal `cial:build` redeploy, `restart('all')` was bouncing the
edge supervisor too — `process.exit(0)` on the supervisor causes Docker
to recreate the entire container, killing the agent's in-flight session
mid-deploy.
`expandRestartTargets('all')` now returns only the four service children
that load rebuilt application code (platform-front, platform-back,
core-front, core-back). Edge bounces only on an explicit `scope: 'edge'`
request. Updated `expectedRestartCount`, the broadcast targets list, and
the `edgeRestart` flag accordingly, plus all referencing docs and the
unrestricted restart skill.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Old-Cial-style dev tenant. Source edits flow through cial:build →
BuildRunner → supervisor restart, the same path prod uses, instead of
relying on tsx watch / next dev --turbopack. Fixes the .next/ clobber
where a concurrent next build (scope=all) crashed the running next dev
core-front and tore the whole tenant down.
- dev-entrypoint.sh: pre-build the full workspace with `pnpm turbo run
build` (was just protocol+sdk+edge); export CIAL_MONOREPO_ROOT for
the next build --turbopack steps.
- supervisor.dev.ts: every child now spawns its built artefact
(node dist/index.js, next start). Updated header doc accordingly.
- next.config.ts (both): refreshed comments — turbopack.root is only
consulted at build time now, not by a running dev server.
- docs: updated dev-tenant, supervisor, and recipes to reflect the
built-artefact model and remove HMR claims.
Previous attempt added \`import path from 'node:path'\` + \`import.meta.url\`
to both next.config.ts files. Next 16's TS-config compiler emitted
CJS-style output (\`exports.default = ...\`), but the package.json
\`"type": "module"\` made Node load it as ESM, crashing on startup with:
Failed to load next.config.ts
ReferenceError: exports is not defined
at <unknown> (next.config.compiled.js:2:23)
Switch to a single env var (\`CIAL_MONOREPO_ROOT\`) read at runtime —
no imports beyond the type-only NextConfig. Same Turbopack-root pin
as before, but the config file stays a literal object that Next's
TS compiler can emit as a no-op ESM module either way.
\`CIAL_MONOREPO_ROOT\` is now propagated to core-front and platform-front
in supervisor.dev.ts (core-back already had it). Outside the dev
container the var is undefined and Turbopack falls back to lockfile
auto-detection, which works fine without bind-mount interference.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Next 16's auto-detection of the workspace root (walking up looking for
pnpm-lock.yaml) misfires inside the dev container — when the anonymous
volume masking core/front/.next gets re-created on restart, Turbopack
mis-infers the project root as src/app/ and crashes with:
Error: Next.js inferred your workspace root, but it may not be correct.
We couldn't find the Next.js package (next/package.json) from the
project directory: /cial/core/front/src/app
Set turbopack.root explicitly to the monorepo root (two levels up from
each next.config.ts) so detection can't go sideways. Recommended fix
per the error message itself.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update API docs, recipes, design doc, deploy-pipeline architecture,
and deploy-logs ops doc to match the new synchronous behaviour
(commit 8505981). The endpoint now returns 200/500 with status,
durationMs, exitCode, errorSummary, and an inline logTail (last
~8KB) — no polling, no companion GET endpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Old behaviour returned 202 with a deployId and expected the agent to
poll GET /deploy/:id — but that route lives on the Better-Auth-gated
deploy module, so localhost curl from inside the container got 401 on
every poll. The agent had no terminal signal and no log visibility,
matching the symptoms Eliot observed (stale-looking session, repeated
401s in core-back logs, no build output).
Mirror old-Cial's ergonomics: one synchronous request, blocks until
the build (and post-build restart) reach a terminal state, returns
{ ok, status, durationMs, exitCode, errorSummary, logTail } with HTTP
200 on success or 500 on failure. logTail is the last 8KB of the
deploy log file so the agent has the failure context inline without
needing a second round-trip.
- DeployService.waitForDone(deployId, timeoutMs): EventEmitter-based
promise that resolves on the next 'done'/'cancelled' event for the
given deployId, or immediately if the row is already terminal.
- DeployService.getLogPath(deployId) + DeployRepository.getLogPath:
surface the persisted log_path for tail reading.
- self/router.ts: await waitForDone, read log tail, respond once.
- cial:build skills (restricted + unrestricted): drop the polling
loop, document the synchronous response shape.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
By default tsc writes the incremental build info to
<package>/tsconfig.tsbuildinfo (next to the tsconfig). In restricted
dev mode /cial/core is RO, so this fails with EROFS — and only the
dist/ subdir is shadow-mounted as writable.
Setting tsBuildInfoFile to "dist/.tsbuildinfo" puts it inside the
writable shadow, matching the convention edge/tsconfig.json already
follows. No behavior change in unrestricted mode or prod (rw paths).
Restricted mode mounts /cial/core as RO, but core-front (Next.js
Turbopack) needs to write a lockfile to .next/, and the entrypoint's
pre-build writes tsc output to core/{protocol,sdk,edge}/dist. Without
shadows, the first restricted run after --reset crashes core-front
with EROFS on the lockfile.
Add anonymous volumes for the four writable paths under core/ when
--unrestricted is not set. Writes land in the ephemeral container
layer; the RO trust boundary on source files is preserved.
This was latent until the recent --reset flow exposed it (cached
.next/ + dist/ from prior unrestricted runs were masking the issue).
Skills now templated under .claude/skills.src/<name>/{restricted,
unrestricted}.md and rendered to .claude/skills/<name>/SKILL.md by
core/scripts/render-skills.mjs based on the active mode. The agent
only sees the variant matching its actual permissions, so it no
longer has to branch on CIAL_UNRESTRICTED on every invocation.
- Dev: dev-entrypoint.sh re-renders on container start; dev-tenant.mjs
masks /cial/.claude/skills with an anonymous volume so writes don't
leak into the host repo bind mount.
- Prod: Dockerfile builder stage renders once (always restricted; prod
doesn't expose --unrestricted).
- Static .claude/skills/ deleted from the repo and gitignored.
- docs/file-structure.md updated with the new layout.
Audit pass over docs/ + adjacent code following the cial-* → core/platform/app
layout consolidation.
Bug fix:
- core/back BuildRunner ALL_FILTERS referenced @cial/core-back and
@cial/core-front, which no longer exist (the packages are @cial/back +
@cial/front). Self-edit deploys with scope=all would have silently
skipped those packages. Filters corrected.
Docs aligned with reality:
- docs/README.md — promotes file-structure.md to the start-here entry.
- architecture/dev-tenant.md — full rewrite: paths now /cial/* throughout,
documents the read-only :ro overlay of /cial/core, the new
--config.confirm-modules-purge=false install flag, the symlink dance for
project skills, and the agent's cwd=/cial + HOME=/cial/data/home setup.
- architecture/deploy-pipeline.md — package-name fix for ALL_FILTERS.
- architecture/core-vs-platform.md — package-name fix for the build list.
- ops/supervisor.md — drops stale "added in Phase 7" annotation.
- ops/deploy-logs.md — example log line uses @cial/back.
- self-edit/recipes.md — protocol path and dependency chain naming.
- design/self-edit-unrestricted.md — banner clarifying it's the original
design record (pre-rename) so an agent doesn't follow stale paths from it.
Tiny code touch:
- core/edge/src/supervisor.dev.ts — comment on CIAL_MONOREPO_ROOT no longer
contradicts itself ("not /cial" → "the bind-mounted repo at /cial").
Build verified: turbo run build for @cial/back still passes (cache miss
re-executed cleanly with the updated runner.ts).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After the cial-* → core/platform/app rename, the named modules volume
from any previous container run no longer matches the lockfile layout.
pnpm 9 detects this and prompts "remove and reinstall? (Y/n)" — which
stalls forever because the entrypoint runs without a TTY, leaving
node_modules empty and the next pre-build step crashing with
"Cannot find module 'zod'" (and friends).
Pass --config.confirm-modules-purge=false so pnpm just wipes and
reinstalls without asking. No behavior change on a fresh volume.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reorganize the dev/prod tenant container so the agent runs in the monorepo
root with a clear, semantic directory tree:
/cial/core/ — runtime (back, front, edge, ui, sdk, protocol, scripts,
docker). Locked down to the cial linux user (mode 0700
in prod; :ro bind mount in restricted dev).
/cial/platform/ — agent-editable surface (back, front).
/cial/app/ — App control plane sources, present in workspace but
never built or run inside the tenant container.
/cial/docs/ — architecture + ops reference.
/cial/.claude/ — project skills/agents/commands (symlinked into the
harness HOME by the dev entrypoint).
/cial/data/ — persistent state (sqlite, deploy-logs, agent home).
Concrete changes:
- git mv cial-core → core, cial-platform → platform, cial-app → app,
scripts → core/scripts.
- pnpm-workspace.yaml: packages now core/*, platform/*, app/*.
- Bulk path rewrites across 250+ source / docker / docs files.
- core/scripts/dev-tenant.mjs: ROOT path fix, rw mount of repo + ro
overlay of /cial/core when --unrestricted is not set (FS-level
trust boundary, defense in depth).
- core/edge/src/supervisor.{ts,dev.ts}: cwd + CLAUDE_HOME relocated to
/cial/data/home; agent runs from /cial root so skill discovery picks
up /cial/.claude/skills automatically.
- core/back providers/claude.ts: HOME defaults to /cial/data/home, cwd
defaults to /cial.
- core/docker/{Dockerfile,Dockerfile.dev,dev-entrypoint.sh}: COPY +
WORKDIR + ENTRYPOINT updated; .claude → harness symlink.
- app/docker/{Dockerfile,Dockerfile.router}: COPY core, COPY app
(instead of cial-core / cial-app).
- New docs/file-structure.md — single canonical map of the runtime
layout. cial:self-edit SKILL.md mandates reading it first.
- cial:build SKILL.md: scope notes updated to platform/* and core/*.
- root package.json: smoke / dev:tenant scripts now under core/scripts/.
- core/scripts/smoke.mjs: cial-core.db → cial.db.
Externals preserved as-is by intent:
- JWT issuer string 'cial-app' in core/back/src/modules/sso/index.ts +
app/api/src/lib/sso.ts is an external contract — NOT renamed.
- @cial/back / @cial/edge / @cial/protocol / @cial/sdk / @cial/front
package names kept stable to minimize blast radius.
Verified:
- pnpm install --prod=false → ok
- turbo run build for protocol, sdk, back, edge, front, platform-back,
platform-front → all 7 successful (Next builds + tsc clean).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Claude harness spawned by sessions/process/providers/claude.ts runs with
HOME=$CLAUDE_HOME (= /var/lib/cial/claude-home) and cwd=$HOME by default.
Slash-command discovery walks up from cwd looking for .claude/skills/, but
nothing under /var/lib/cial/... sees /workspace/.claude/. Result: skills
shipped in the repo (cial:self-edit, cial:build, cial:restart) appeared as
"Unknown command".
Entrypoint now symlinks the bind-mounted project .claude/* into the harness
HOME so they're discovered at the user level. Symlinks (not copies) so live
edits via the bind mount take effect on next session spawn without rebuild.
Adds the two utility skills the agent invokes from the master cial:self-edit
flow: cial:build POSTs /api/v1/self/deploy and polls until terminal,
cial:restart POSTs /api/v1/self/restart and handles the edge-bounce case.
Both target core-back directly on :4000 because edge only proxies /.cial/api/*,
not /api/v1/*.
Widens DeployStartEvent.targets, DeployRestartStartEvent.service, and
DeployRestartDoneEvent.service in @cial/protocol to include core-front,
core-back, and edge so the broadcast events emitted during scope=all
deploys typecheck. Trust enforcement still lives in the supervisor, not
the schema.
Adds POST /api/v1/self/deploy and /api/v1/self/restart on cial-core for
agent-initiated builds and restarts. Introduces CIAL_UNRESTRICTED=1
(opt-in via `pnpm dev:tenant --unrestricted`) which widens the trust
boundary so the agent can rebuild and restart core+sdk+protocol+edge
in addition to platform.
Trust boundary enforced at three layers:
- BuildRunner pnpm filter (platform vs all)
- Supervisor IPC RESTARTABLE_SERVICES set
- localhost-only middleware on /api/v1/self/*
Edge restart uses 50ms-deferred process.exit so Docker restart-policy
bounces the container. Dev supervisor gained the IPC server it was
silently missing.
Ships docs/ tree (architecture, self-edit, ui, ops) and the
cial:self-edit Claude skill, both copied into the dev+prod images so
the in-container agent can read them before editing.
Sidebar:
- SessionRow → rounded-xl + py-1.5 + mb-0.5; 3px indigo active bar with glow
- Port SidebarTimer (live MM:SS in indigo busy pill) reading streamingStartedAt
- Replace 7×7 + icon with full-width "New session" CTA; drop Sessions caption
Chat:
- MessageList: drop hardcoded max-w-5xl so messages span full pane
- StatsFooter: surface costUsd via new fmtCost (Coins icon)
- MessageRow: hover-revealed Copy button next to AssistantHeader (Copy → Check)
- InputBar: drop "Cial can make mistakes…" disclaimer (not in old)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a Unix-socket protocol (default /run/cial-supervisor.sock, mode 0660
cial:cial) so Core Back can ask the supervisor to bounce platform-front /
platform-back without taking the container down. Only platform-* children
are restartable; core-back / core-front / edge are rejected. Exit handler
distinguishes requested restarts (respawn) from crashes (existing
crash-the-container behaviour preserved).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces the always-visible ToolList stack with a single
"Process (N steps)" pill below each finished assistant message.
Click to expand a panel listing reasoning + every tool call (with
input/output) + response, mirroring the legacy Cial UX.
Multi-turn answers show "(X turns, X steps)" and group by turn.
SDK re-exports SubTurn so the UI can type the metadata.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- getStatusLabel + ActiveNotch now filter pendingTools by output==null
before picking the "active" call, so the notch correctly flips to
Writing once tools settle (was sticking on the last tool's name).
- Streaming timeline moved inside StreamingBubble / ThinkingBubble,
rendered between AssistantHeader and the body — matches legacy Cial
layout where tools sit under "Cial" and above the answer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- StreamingBubble: paced reveal now varies 0.6×–1.4× per frame so the
typewriter feels organic instead of metronomic.
- StreamingTimeline: subtle vertical dots+line of pending/done tool
calls (label · check · duration), shown above the streaming
response. Adds a "Writing response" step once tools settle. Last 3
visible by default; older steps collapse behind "+ N earlier
steps". Replaces the heavy ToolList during streaming — full
ToolPills stay for completed messages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Decouples visible text from upstream chunk size. A rAF loop reveals
characters at ~220 chars/sec regardless of how big each backend
delivery is, with a burst-catch-up when the backlog grows past 600
chars so long answers don't fall behind. Resets on new turn.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Drops TAIL_POLL_MS from 500ms to 50ms (env-overridable) so text
flushes ~20 Hz instead of 2 Hz. fs.statSync + readSync on the
per-turn log is negligible. StreamingBubble now memoises
renderMarkdown(text) so the tighter tick rate doesn't reparse the
buffer on every parent re-render.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Picker now lives outside the composer shell as its own subtle pill row,
solid background (not transparent) so it reads as a real control.
Chat content widens to max-w-5xl while the input stays max-w-3xl
centered, matching the legacy Cial layout.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Per platform.claude.com (models overview) and code.claude.com
(--effort flag in CLI reference):
- effort enum: drop 'normal' (not a CLI value); add 'low', 'medium',
'max' so the wire matches the official 5-level set
('low' | 'medium' | 'high' | 'xhigh' | 'max')
- models.ts: surface all 5 effort options in the picker, strongest
first; default stays 'xhigh'
- models.ts: add legacy models (Opus 4.6, Sonnet 4.5, Opus 4.5,
Opus 4.1) as picker options tagged legacy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- protocol: extend SessionEffort enum with 'xhigh' (matches legacy max)
- back/service: new sessions default effort to 'xhigh' so the harness
always boots at max reasoning unless the user picks otherwise
- sdk: chatSend + create accept 'xhigh' on the effort union, plus
effort field on create() so per-session config can be set up front
- ui/store: thread per-turn model + effort overrides through
sendMessage/newSession; expose ChatSession.effort to consumers
- ui: new ConfigPicker (model + effort dropdowns) embedded in InputBar,
with state owned by SessionView and synced from active session
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- store: stream.tool_result merges output+durationMs onto the existing
tool entry (was filter, causing tools to flash and disappear mid-stream)
- store: subscribe stream.start to patch session.model from the wire
- store: subscribe stream.usage and surface live token counts via new
ChatSession.streamingUsage; cleared on stream end
- ActiveNotch: render live "{X} in · {Y} out" tokens alongside model
- MessageRow: collapsible ReasoningBubble for completed metadata.thinking
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Cap `ToolList` at 4 visible pills; older calls collapse behind a
paper-stack "+ N more tool calls" button (mirrors legacy UX). Click to
expand inline. Stack styles in the new `Tools.scss`.
- Add timestamp on every message row — right-aligned "You · 14:32" above
the user bubble; "Cial · 14:32" inline in the assistant header.
Timestamps use `Date.now()` ms (no `* 1000` like legacy).
- New `StatsFooter` rendered under each completed assistant message:
short model · turn count · duration · tokens (in/out). Driven by
`message.metadata.stats`.
- New `lib/format.ts` helpers: `formatTime`, `fmtTokens`. SDK now also
re-exports `TurnStats` / `TurnUsage` for downstream consumers.
- `lib/tools.ts` — `toolLabel(tool)` produces a human label across the
Claude/Kimi/Gemini name variants ("Reading file.ts", "$ ls", "Grep …",
"Skill: /foo", "mcp__server: action"). `toolIcon(name)` returns the
matching lucide icon with a per-category color. Plus `fmtDuration` and
`isPendingTool` helpers.
- `components/Tools/ToolPill.tsx` — single expandable tool row built on
`<details>` (kbd + a11y free). Header shows icon + label + spinner/check
+ duration; body shows pretty-printed input then output (max 4kB
truncated, scrollable).
- `components/Tools/ToolList.tsx` — animated vertical stack of pills, used
both for completed messages (`message.metadata.tools`) and live
streaming (`session.pendingTools` with `animated`).
- Wired into `MessageRow` (assistant tools) and `MessageList` (pending
tools below the streaming bubble).
- Add `streamingStartedAt` + `model` to `ChatSession`. The store sets
`streamingStartedAt` synchronously on `sendMessage` so the ActiveNotch
appears the instant the user submits and stays visible across the gaps
between tool calls (it used to flicker because the previous check ANDed
`streamingText`, `pendingTools`, and `status === 'busy'` — all three lag
behind submit). Cleared on `stream.message` / `stream.cancelled` /
`stream.error`.
- ActiveNotch now uses `streamingStartedAt` for the live timer (was using
`updatedAt` which ticks on every server event) and renders the short
model id next to it via a new `lib/format.ts#shortModel`.
- MessageList scroll: switch to `requestAnimationFrame` + `instant`
during streaming so successive token chunks don't fight an in-flight
smooth scroll; smooth otherwise. Also include `pendingTools.length` in
the change-detection so the notch movement triggers a scroll.
- Float the InputBar absolutely over the bottom of SessionView so the
chat takes the full height of the pane while the composer stays
centered. Add `pb-44` to MessageList content for the overlap.
Reorganize @cial/core-ui from a flat src/ into three folders:
- components/<Name>/ — tsx + colocated .scss per component, splitting
MessageList (240→60 lines + 6 leaf files) and
Sidebar / SessionView into their natural pieces
- lib/ — store, theme, markdown, types
- styles/ — _tokens, _base, _animations, _components,
index.scss (the SCSS bundle entry)
Move all custom CSS (theme tokens, glow border, hljs theme, prose-chat,
keyframes, .cial-logo, .bubble-*, .composer-shell, .send-button,
.cial-avatar, .empty-state-icon …) into the package's SCSS partials so
the package travels with its own styles. Replace inline gradient
style={{ background: 'linear-gradient(...)' }} usages in AppShell,
Sidebar avatar, SessionView empty-state and InputBar send button with
proper SCSS classes.
Expose `@cial/core-ui/styles` via the package.json exports map and
import it from cial-platform/front via a tiny cial-styles.scss alongside
globals.css. globals.css now owns ONLY Tailwind setup (the framework
import, @source for cross-package class scanning, @theme inline tokens
and the two @utility scrollbar rules — none of which Sass can parse).
Add `sass ^1.83.4` as a dev dep on cial-platform/front; Next.js 16
picks it up automatically. No public API change — `AppShell` and
`SessionUser` are still the only exports.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
InputBar overflow:
- SessionView's active branch used h-full inside a flex container that
also held InputBar as a sibling, pushing the composer below the
visible viewport. Replaced with `flex-1 min-h-0` so the message area
shares space with the composer.
ActiveNotch phase parity:
- Re-export Tool from @cial/sdk.
- ChatSession now tracks `pendingTools: ReadonlyArray<Tool>`, populated
from `stream.tool_use` and drained on `stream.tool_result`. Cleared
on `stream.message`/cancelled/error.
- theme.ts now mirrors legacy /app/client activityTheme.ts: 9-phase
palette (Thinking/Writing/Researching/Editing/Executing/Browsing/
Delegating/Running skill/Working) with phase-specific colours +
glow + dot, plus `getStatusLabel(streaming)` derivation rule
(Read/Glob/Grep → Researching, Edit/Write → Editing, Bash →
Executing, WebSearch/WebFetch → Browsing, Agent/Task → Delegating,
Skill → Running skill).
- ActiveNotch reads phase via getStatusLabel + getActivityTheme and
shows the active tool name beside the phase label on md+ screens.
Replaces the inline-CSS placeholder UI with a full Tailwind 4 + motion/react
chat surface:
- AppShell uses theme tokens from globals.css (no more dangerouslySetInnerHTML)
- Sidebar: animated brand, motion-driven session row hover/delete reveal,
layoutId active bar, lucide icons, connection dot
- MessageList: motion entry on bubbles, streaming bubble with blinking
caret, sticky floating ActiveNotch overlay (animated glow border + live
timer) shown during streaming
- InputBar: composer-shell with focus ring, motion send/stop button morph
with spring, auto-resize textarea
- Tailwind 4 wired into platform-front via @tailwindcss/postcss + @source
directive pointing at the cial-core/ui workspace package
Inside the tenant container claude runs as root, and refuses
`--dangerously-skip-permissions` with "cannot be used with root/sudo
privileges for security reasons". IS_SANDBOX=1 is its documented bypass
— same flag the legacy server used.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The entrypoint tried `pnpm exec tsx supervisor.dev.ts` but tsx is only
in cial-core/back + cial-platform/back devDeps, not at the workspace
root — `tsx not found`. The edge prebuild (`tsc -b`) already compiles
supervisor.dev.ts → dist/supervisor.dev.js, so just node-exec the JS.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dev:tenant now runs a single Linux container that mirrors the prod
tenant container shape (5 processes, port 8080) but with hot reload,
single root user, and credentials injected from the host's macOS
Keychain on startup. Source is bind-mounted; Linux-built node_modules
live in named volumes so they don't collide with macOS-built ones.
New files:
cial-core/docker/Dockerfile.dev single-user dev image (claude binary baked in)
cial-core/docker/dev-entrypoint.sh creds → ~/.claude → pnpm install → pre-build → exec supervisor
cial-core/edge/src/supervisor.dev.ts container-side supervisor with watchers
Rewritten:
scripts/dev-tenant.mjs extracts host keychain + drives docker build/run
Volumes (survive Ctrl-C, wiped by `node scripts/dev-tenant.mjs --reset`):
cial-dev-tenant-state /var/lib/cial (sqlite db, claude home)
cial-dev-tenant-modules /workspace/node_modules + per-package shadows
cial-dev-tenant-pnpm-store /pnpm-store (install cache)
Trade-offs:
- First boot is slow (pnpm install in-container). Subsequent boots fast.
- protocol/sdk/edge dist files are written through the bind mount to the host.
- macOS Keychain stays the source of truth for credentials; the container's
OAuth refreshes don't propagate back.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
hasLocalState was hardcoded to look at `~/.claude/projects/-root/<id>.jsonl`
— claude's path-encoding of /root, which only matches the legacy server's
container cwd. On macOS the encoded subdir is `-Users-<user>`, so the
check always returned false → engine fell back to `--session-id` for an
id that already existed → claude silently collided and the second turn
hung.
Scan every subdir of projects/ instead. Also bump the spawn log to
info-level and warn on non-zero exit so harness errors are visible
in dev:tenant output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous fallback (`<coreDataDir>/claude-home`) forced ClaudeProvider
to override HOME even in dev:tenant — re-introducing the macOS Keychain
breakage that the dev-tenant fix was supposed to resolve. Production
containers always set CLAUDE_HOME explicitly per tenant, so the fallback
only applies to developer boxes, where pass-through HOME is what we want.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>