Conversation
…files Relocate the two directory-local subsystem sections out of root CLAUDE.md into server/lib/aiToolkit/CLAUDE.md and client/src/components/dashboard/CLAUDE.md so their detail loads on demand instead of in every session. Root keeps a discovery pointer for each. No guidance lost.
…y clients, named constants, a11y labels
From the 2026-06-09 audit. history routes drop the hand-rolled
.catch(next) pattern for asyncHandler (404 via ServerError instead of a
silent hang on a falsy result) with error-path tests added. The
Moltworld/Moltbook integration classes become factory functions per the
no-classes convention (legacy names kept as aliases). Magic numbers get
named constants (Moltbook action delay, Outlook full-body refresh limit
+ truncated flag, Apple Health XML poll/timeout, self-restart delay).
Settings tabs get proper label/input pairing and aria attributes, and
swallowed .catch(() => {}) sites get breadcrumb logging.
From the 2026-06-09 audit. Crash-safe atomicWrite replaces raw writeFile for a dozen state files (memory index/embeddings, backup scheduler state, calendar/message accounts, activity logs); a shared sleep(ms) in fileUtils replaces seven per-module delay one-liners; tryReadFile/safeJSONParse replace hand-rolled read+parse combos. Client formatter duplicates (formatBytes, formatCooldown, formatDurationMin/Ms, parseSizeGb/recommendedRamGb) collapse onto utils/formatters with new unit tests, and the agents World tab fixes a stale closure so history reloads when switching accounts. New test files cover memoryStore and the account services.
…SE write guard From the 2026-06-09 audit. Async setTimeout callbacks in agent lifecycle/spawning get try/catch so a rejected updateAgent can no longer kill the process. Provider test/model-list fetches and the Ollama release lookup get AbortSignal timeouts so a stalled local endpoint can't hang forever. The aiToolkit drops its one outward import (antigravity constants) for a documented internal copy, restoring the vendored package's self-containment. The voice dictation socket handler gets the mandatory non-request-path try/catch, the logs SSE stream guards writes after client disconnect, mergeIssuesFromSync persists only the issues the merge actually changed, and the task-override fan-out is bounded with mapWithConcurrency. New tests cover the provider network layer, voice payload validation, the spawn initialization timeout, and changed-only sync persistence.
From the 2026-06-09 audit. GoalsTreeView, BrainGraph, the five Chief of Staff avatar variants, and the CoS MemoryGraph now load on demand (static barrel re-exports removed so the dynamic imports actually split — Rollup flagged them as ineffective). MemoryTab's icon-only buttons and search input get aria-labels, the prompt editor's clickable div becomes a real button, and roadmap section keys are stable. .env.example replaces two phantom OPENCLAW vars with the nine the server actually reads and documents 16 previously-missing env vars (verified against code).
…kages From the 2026-06-09 audit. The 194 MB googleapis package shipped 323 API surfaces of which PortOS uses two; the official scoped @googleapis/calendar + @googleapis/gmail + google-auth-library packages provide identical call signatures at a few MB. Also bumps the stale exact sax pin to ^1.6.0, aligns server pm2 to 7.0.1 (matching the root manifest), and switches goalCalendarScheduler's goals.json write to atomicWrite while in the file.
… upload paths From the 2026-06-09 audit. executeCommand now builds its child env via safeChildProcessEnv like every other spawn site (Malloc debug noise stripped; PATH/HOME/auth tokens intentionally inherited — allowlisted commands like gh/git/npm need them, and the choice is now documented at the call site). The uploads endpoints return /api/uploads/<name> URLs instead of leaking absolute server filesystem paths. Includes the audit's changelog entry.
…rvices From the 2026-06-09 audit. The 214-line imageGen /generate handler delegates to a new services/imageGen/prepareParams.js; the clean / de-watermark / light-regen variant endpoints share a new services/imageGen/variants.js (persist-variant tail deduplicated); the SSE writeHead/send/safeEnd boilerplate in imageGen/videoGen collapses onto a shared openSseStream helper in sseDownload.js. llmSchema moves from a sibling-route import into lib/validation.js, episode canon extraction moves from the pipeline route into arcPlanner, and the /templates page registers in the nav manifest so it is reachable from the command palette and voice navigation. imageGen tests pin the avatar response shape and add a regenerate happy-path contract.
…in sub-routers
server/routes/pipeline.js had grown to ~2,484 lines / 83 handlers. Split it
into server/routes/pipeline/{audio,series,arcs,manuscript,covers,issues,
editorial}.js assembled by index.js, mirroring the cos.js sub-router pattern.
Pure code motion: route paths, schemas, and handler bodies are unchanged
(verified by diffing the extracted method+path sets — identical 83 entries).
Shared error mapping (mapServiceError), the provider-override schema shape,
and countExtractedCanon moved to pipeline/shared.js. Comment pointers in
sharing.js, migrateSeriesCanon.js, tts.js, and promoteToPipeline.js updated
to the new file locations.
Refs #1081
refactor([issue-1081]): split routes/pipeline.js god-router into domain sub-routers
refactor: history asyncHandler, factory integration clients, named constants, settings a11y
refactor: consolidate duplicated helpers onto shared canonical utilities
fix: crash/hang guards — async timer callbacks, fetch timeouts, SSE write guard, bounded fan-out
perf: lazy-load three.js views; a11y fixes; accurate .env.example
deps: scoped @googleapis packages replace the googleapis monolith; sax/pm2 alignment
# Conflicts: # .changelog/NEXT.md
security: safer executeCommand env handling + relative upload paths
# Conflicts: # server/routes/pipeline.js
refactor: extract imageGen/pipeline business logic into services; register /templates in nav
…amed helpers Extract each spawn priority tier from the ~430-line evaluateTasks loop into a named private function (spawnPriority0OnDemand, spawnPriority1UserTasks, spawnPriority2AutoApproved, spawnPriority3Missions, spawnPriority36FeatureAgents, spawnPriority4IdleReview), plus resolveAutonomyBudget and unblockExpiredOrphanCooldowns. evaluateTasks now orchestrates the tiers in sequence over a shared spawn context (ctx), keeping the cross-cutting gates (paused/daemon, global slot cap, autonomy + daily budget) in the orchestrator so they uniformly cover every tier. No behavior change. Repoint the source-level invariant guards in cos.test.js at the new helper scopes (the engine's logic moved out of the evaluateTasks body).
/simplify pass — no helper destructures ctx.userTaskData (unblock pass takes it directly; tiers use pendingUserTasks).
…ew: claude,codex) The two budget/idle invariant guards were widened to whole-module GEN_SRC when the gate logic moved into spawnPriority* helpers, losing the 'is this fenced helper actually wired into evaluateTasks?' precision. Add an orchestrator tier-order guard (mirroring the existing dequeueNextTask one) so a fenced helper can't drift out of the spawn path while the module-scoped gate guards still match its orphaned fence text.
refactor([issue-1082]): decompose evaluateTasks priority tiers into named helpers
…district layout cityPlan.js now owns CyberCity's geography: every district helper anchors its parcel there instead of hardcoding a compass position, and invariant tests enforce world bounds, plaza clearance, and shoreline placement. The north edge (-Z, facing the default camera) becomes a bay: CityWater renders the water plane + surf line, the ground/grid clip at the shoreline, skyline silhouettes skip the water, and the voice beacon steps aside for a plaza-to-harbor avenue. Street/transit topology is computed here for the upcoming street-fabric and Data Harbor commits.
Read-only diagnostics for the upcoming Data Harbor district: public-schema tables (pg_stat_user_tables row estimates + relation sizes, pgvector column flags, database size, applied migrations) and per-domain disk usage of data/. 45s TTL cache with stale-while-revalidate so the multi-second data/ walk on media-heavy installs never blocks the route; a down DB yields db:null rather than empty arrays per the absent-vs-empty rule.
…ta/ filesystem One disk-stack silo per Postgres table on the western pier (stack height = log-scaled rows, disk radius = log-scaled size, orbiting ring on pgvector tables, migration obelisk at the pier head) and one container rack per data/ domain on the eastern pier (lit slats = log-scaled disk usage). Clicking a silo/rack opens a holographic detail card. A down DB renders a pulsing DB OFFLINE beacon, distinct from a reachable-but-empty database. Fed by GET /api/city/introspection on a 2-minute page poll.
…ftop kits The town fabric layer, all derived from the master plan: an octagonal ring road with spokes to every district and a grand avenue to the harbor (3 merged draw calls: asphalt / neon edge strips / vertex-colored paint+district pads), an elevated transit loop with orbiting trams and station pylons, instanced lamp posts with faked light pools plus plaza holo-trees (4 draw calls at any density), and deterministic per-app rooftop fixtures (antenna/tank/AC/dish, seeded like the window textures). Props and trams gate off on the low quality preset; everything follows cityDayMix for the day/night looks.
- introspection fs section delegates to dataManager.getDataOverview (du/find) instead of a third hand-rolled JS directory walk — harbor and Data page now report identical numbers, and the slow recursive-stat path is gone - harbor helper owns ALL pier geometry (decks sized to contain their structures, obelisk position, quay offsets) so the component only renders; silo colors resolve through the theme palette's getAccentColor instead of a drifted private copy; two-row layout deduped; log scaling delegates to scaleMetricToHeight; formatCompactCount added to formatters - transit stops derive from parcel anchors (move a parcel, the tram stop follows) with an invariant test pinning the derivation - cityShowDetail(settings) single-sources the above-low-preset gate used by rooftops/trams/street props; WORLD owns the land extent + ground y-stack; plaza pad exclusion is a parcel flag, not an id check; gangway width reuses AVENUE_WIDTH; instanced-prop matrices no longer rewrite on every re-render; introspection poll keeps payload identity via useAutoRefetch compare
…seProgress divergence
refactor([issue-1087]): extract shared useInstallStream hook for SSE install modals
…m flow Covers settings save (success + error toast), the DB restore-confirm modal gate (dry-run preview opens the modal; no destructive restore on cancel; real restore only after explicit confirm; no-dump abort), and pgBackup/degraded status rendering.
test([issue-1088]): behavioral tests for BackupTab restore-confirm flow
…S_WORKSPACE_ROOTS scoping Extract the workspace-root allow-list (defaults + PORTOS_WORKSPACE_ROOTS, symlink-resolved containment) out of routes/commands.js into a shared lib/workspaceRoots.js helper and reuse it in both routes. detect/repo now carries a trust-model comment and, when PORTOS_WORKSPACE_ROOTS is set, confines detection to the configured roots (returns valid:false otherwise); unset, it stays unrestricted as before.
…-in gate - detect.js: wrap realpathSync in try/catch so a TOCTOU/permission edge returns the route's valid:false shape instead of a 500 (matches commands.js + the route's own contract for unusable paths). - workspaceRoots.test.js: cover WORKSPACE_ROOTS_CONFIGURED + EXTRA_WORKSPACE_ROOTS parsing and the configured-roots allow-list fold (the core feature of #1089) via stubbed-env re-import.
feat([issue-1089]): document detect/repo trust model + optional PORTOS_WORKSPACE_ROOTS scoping
…ordance Convert the diff and release-confirmation modals to the shared ui/Modal component, which supplies role=dialog + aria-modal on the panel, role=presentation on the backdrop, Escape-to-close, and click-outside. Add aria-label to the × close buttons and aria-labelledby wiring to the titles. Adds Modal.test.jsx pinning the dialog roles, labelling, and Escape/backdrop close behavior.
…Tab call-site test - Pass align="none" on both modals so the backdrop has no p-4 padding, exactly matching the pre-refactor bare-overlay centering (claude review). - Add GitTab.test.jsx asserting the diff and release dialogs render with dialog roles, aria-labelledby titles, a labeled Close button, a presentation backdrop, and Escape-to-close — pins the call-site wiring, not just the shared Modal (both reviewers flagged the gap).
fix([issue-1090]): GitTab modal dialog roles + close affordance
…t moderates Adds a qs@6.15.2 override and bumps the brace-expansion override to 5.0.6, resolving GHSA-q8mj-m7cp-5q26 (qs stringify DoS) and GHSA-jxxr-4gwj-5jf2 (brace-expansion range DoS). Both are transitive, non-breaking fixes (express/body-parser/googleapis accept the patched qs; brace-expansion 5.0.6 is a patch bump). Drops server npm audit moderates from 11 to 7; the remaining 7 (pm2 -> ws, kokoro-js -> @protobufjs/utf8) stay blocked on upstream majors as documented in the issue.
fix([issue-1091]): pin patched qs + brace-expansion to clear npm audit moderates
Adds a Staff↔Piano toggle to the Song reader's multi-part player. Piano mode renders every selected layer as colored notes falling onto a piano keyboard and lights each key as its note crosses the hit line — the aggregate of all checked voices played on the piano at once. Each layer gets a stable color shared by the falling notes, the keyboard glow, and a swatch on its layer checkbox. The fall reads the player's live position() each frame so it stays sample-aligned to the audio. Keyboard geometry + per-note MIDI mapping live in a new pure, unit-tested pianoKeyboard.js helper; buildSchedule now carries a midi per note and createMultiScorePlayer gained position()/duration() accessors.
getBackend() fell back to the file backend under NODE_ENV=test only when Postgres was unreachable. On a dev machine with a healthy local 'portos' DB the suite wrote hundreds of fixture universes/series/works straight into the real database, federated them to live Tailscale peers via autoSubscribeRecordToAllPeers, and let test-cleanup tombstones delete the user's real records. Test mode now short-circuits to the file backend before the health probe. An explicit MEMORY_BACKEND=postgres still opts a suite into the PG path. Adds a regression test asserting test mode bypasses a healthy Postgres, and a one-off recovery script to restore tombstoned-real records, purge fixtures, and prune the peer-subscription ledger on an already-polluted install.
…pre-roll in the clock - Clamp the keyboard band to height so a small height prop can't produce a negative hitLineY (inverted fall). - Read draw() via a ref in the resize effect so the ResizeObserver is created once per height rather than re-subscribed on every notes/tempo change. - Add a test exercising the playing=true rAF loop and its cancelAnimationFrame cleanup on unmount. - Keep the LEAD-in pre-roll in the piano-roll clock.
fix(memory): never use a developer's real Postgres under the test runner
feat(songs): Synthesia-style piano-roll view for the layered MIDI player
…zed column listUniverses/listSeries read deleted-state from each record's `data` JSONB (`SELECT data FROM universes`), not the row's denormalized `deleted` column. The recovery script only flipped the column, so 'restored' universes stayed invisible in the UI (their data.deleted was still true) and a second --apply reported 'restore 0' because the column no longer matched deleted=true. Restore now clears data.deleted + data.deletedAt, bumps data.updatedAt, and resyncs the columns; detection and the live tally key off the JSONB source of truth.
…eted fix(recovery): restore the JSONB data.deleted, not just the denormalized column
…ehind cards The 'Subscribe to peer' (cloud) and 'Share to bucket' popovers were absolutely positioned inside each series/universe card. Cards establish their own stacking context (translucent/blur background), so an in-card `absolute z-30` popover is painted UNDER the following sibling card and is unusable. Both dropdowns now render via createPortal into document.body with fixed positioning anchored to the trigger button's viewport rect, re-anchored on scroll/resize, and the outside-click handler checks the portaled menu too. Also falls back to host/address/'Unnamed peer' when a peer has no name (was rendering the literal 'undefined').
fix(sharing): portal peer Sync/Share dropdowns so they don't render behind cards
…e removed googleapis package
The googleapis monolith was dropped from server/package.json in favor of
the scoped @googleapis/* packages, but executeGmailApiAction still did a
dynamic import('googleapis'). Boot survives (dynamic import), so the
breakage only surfaces on the first Gmail archive/delete as a runtime
'Cannot find module googleapis'. Mirror the @googleapis/gmail client
pattern already used in messageGmailSync.js.
Also guard the one-off recovery script's peer_subscriptions prune with an
Array.isArray check (it runs after an irreversible DB commit), and fix a
stale resolveBoomT -> resolveBoom doc-name reference in the city rig.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release v2.19.0
Released: 2026-06-09
Added
pianoKeyboard.js); the multi-score player gainedposition()/duration()accessors.cityPlayerRig.js).cityPlan.js— single source of every district's parcel, with invariant tests). The north edge is a bay: animated water, a shoreline that clips the ground grid, and a new Data Harbor pier district visualizing the install's storage — one disk-stack silo per PostgreSQL table (height = rows, radius = size, orbiting ring on pgvector tables, migration obelisk) and one container rack perdata/domain (lit slats = disk usage), with clickable holographic detail cards and a distinct "DB OFFLINE" state, fed by a newGET /api/city/introspectionendpoint. Town fabric: an octagonal ring road with spokes to every district and a grand avenue to the harbor, an elevated transit loop with orbiting trams, instanced street lamps with light pools, plaza holo-trees, and deterministic per-app rooftop fixtures. Everything follows the existing theme-driven day/night system and quality presets.Changed
--system-prompt .), skipping the heavyweight default harness prompt for interactive sessions. A new claude (full) button preserves the previous behavior (Claude's default system prompt) for when it's wanted.googleapispackage replaced by the scoped Calendar/Gmail/auth packages (~190 MB less on disk); screen-reader labels on icon-only buttons and form fields across settings tabs; agent World-tab history now reloads when switching agents; and ~120 new tests covering previously untested services.CLAUDE.mdinto directory-scopedserver/lib/aiToolkit/CLAUDE.mdandclient/src/components/dashboard/CLAUDE.mdfiles (load on demand when working in those subtrees), leaving discovery pointers at root. Trims root context with no loss of guidance.PORTOS_WORKSPACE_ROOTS, confines detection to those directories — pointing it elsewhere returns an invalid-path result instead of reading the directory. Unset (the default), detection stays unrestricted as before.Fixed
npm audit, with no breaking changes. A couple of remaining advisories stay blocked on upstream projects publishing compatible major releases.memoryBackend.getBackend()selected the file backend underNODE_ENV=testonly when Postgres was unreachable — so on any dev machine with a healthy localportosDB the suite wrote hundreds of fixture universes/series/works straight into the real database, fanned them out to live Tailscale peers viaautoSubscribeRecordToAllPeers, and let test-cleanup tombstones delete the user's real records. Test mode now short-circuits to the file backend before the health probe, so a running dev DB is never touched (an explicitMEMORY_BACKEND=postgresstill opts a suite into the PG path). Adds a regression test asserting test mode bypasses a healthy Postgres, plus a one-off recovery script (scripts/recover-synced-test-fixtures.mjs) that restores tombstoned-real records, purges fixtures, and prunes the peer-subscription ledger.document.bodywith fixed positioning anchored to their trigger (re-anchored on scroll/resize), and a peer missing its name shows a sensible fallback instead of the literal "undefined".Full Changelog
Full Diff: v2.18.0...v2.19.0