diff --git a/.gitignore b/.gitignore index 2064372c..bf051dd0 100644 --- a/.gitignore +++ b/.gitignore @@ -42,7 +42,7 @@ Thumbs.db tmp/* # Local agent troubleshooting notes -.papercuts/ +**/.papercuts/ # Local git worktrees .worktrees/ diff --git a/.papercuts/troubleshooting.md b/.papercuts/troubleshooting.md deleted file mode 100644 index 91b39eda..00000000 --- a/.papercuts/troubleshooting.md +++ /dev/null @@ -1,227 +0,0 @@ -# Troubleshooting - -- Pi 0.80.10 can choose the oldest oversized user turn as `firstKeptEntryId`, leaving both summary inputs empty and producing a no-op checkpoint. When the journal has a newer turn, retry `prepareCompaction` with a minimal retained-tail budget; still refuse the checkpoint if both summary inputs remain empty. -- `Session.getEntries()` includes abandoned branches. Synchronization markers must be read from `Session.getBranch()` or a rolled-back partial write can still look committed. -- Child-runtime unit tests load outside Electron. Keep usage accounting behind an injected callback (with a production-only dynamic import) instead of statically importing the Electron-backed singleton into the reusable child registry. -- A compaction model can overflow on the very history it is supposed to summarize. Strip binary images first and map-reduce serialized fragments within a conservative fraction of the model window before the final Pi checkpoint call. -- Renderer-safe truncation markers must be normalized before their length is budgeted, then the truncated value must be sanitized again; NFKC expands `…` to `...` and can otherwise invalidate an exact-length snapshot. -- Packaged builds do not initialize `aiden-dev.log`. Subagent process failures instead write one redacted, owner-only JSONL record to `logs/subagent-runtime.log`; correlate its `SA-*` diagnostic ID, closed stage/code, attempts, timing, and exit status with the Pi journal and V2 run store without logging task or report contents. -- Pi 0.80.10's public `AgentHarness` cannot express Aiden's global sequential-tool policy or resume an already-journaled user tail. Keep the low-level Agent behind `PiAgentRuntimeHarness` until Pi's durable Harness implements restore/resume, tool execution policy, and automatic between-turn compaction. -- Pi's remote model catalog can evolve independently of its agent/session runtime. A full package bump from 0.80.10 exposed breaking Session contracts, so backport the bounded provider-catalog wrapper and any explicitly reviewed transport adapters behind Aiden's registry; do not couple a model-list refresh to an unplanned journal/session migration. -- A provider-scoped catalog refresh must not call Pi's mutating `getAuth()` merely to test configuration: expired OAuth can rotate credentials after the caller's timeout. Use the non-refreshing auth check, resolve staleness before auth, keep provider-owned catalogs out of launch polling, cache valid empty/negative results, and bound HTTP generation timestamps against wall-clock skew so a far-future validator cannot freeze recovery. -- Pi awaits `Agent.subscribe()` listeners. Never register an optional renderer/plugin observer as a critical listener: isolate it through the runtime contribution observer path, or its exception can alter the model lifecycle. -- A Pi observer is not passive merely because its return value is ignored: a live `tool_execution_end` event contains mutable result references, and awaiting the observer blocks Pi settlement. Clone observer events and dispatch them outside Pi's serial lifecycle. -- Global extension tools cannot be copied into a child Agent after authority is minted. Keep child contributions disabled until an adapter can include their names, effects, approvals, schemas, and budgets in the immutable child ceiling. -- Pi's internal idle promise can resolve just before the facade's outer prompt operation classifies hook/subscriber faults. Destructive callers must await both boundaries before reset, deletion, or another prompt. -- A Pi `beforeToolCall` block is non-terminal by default: Pi emits an error tool result and can ask the provider again. If the approval/policy hook itself fails, record the host fault and make the next-turn preparation throw so no second request crosses the broken policy boundary. -- Repair retryable assistants before appending the next visible user or opening its crash-recovery envelope. Once a new user is the leaf, exact failed-assistant abandonment is no longer safe. -- Awaiting only the provider signal does not make session I/O cancellable. Race seed, append, context-build, compaction, and retry hooks against the facade's one managed abort signal, while keeping external effect evidence behind the visible-turn rollback repair. -- Rolling back an inner Pi message transaction can delete a later commit for an enclosing visible-turn envelope while leaving its begin marker. When abandoning a committed failed assistant, re-close only enclosing transactions proven committed on the original branch; never close the current in-flight lease. -- A non-abortable journal append can settle after `app_cancelled`. Track it independently of the managed outcome and quarantine that chat's journal until the write settles and rollback/reconciliation completes; resetting the Agent alone is not a storage barrier. -- Foreground prose aggregates multiple Pi assistant/tool turns, while the durable tail contains only the final assistant. Reconcile that aggregate only when the runtime explicitly reports an abandoned terminal Pi message; doing it before every marker duplicates healthy tool-loop context. -- Electron `utilityProcess` does not provide an owned POSIX process group. Patch Node child-process entry points before provider modules load so Bedrock `credential_process` fails closed instead of spawning an unowned descendant, and bind hard-kill escalation to the captured launch identity rather than a reusable raw PID. -- An effect journal is not crash recovery until its uncertain records change future model context. After Pi rolls back an incomplete visible turn, install an idempotent private no-repeat boundary before accepting another prompt; mark the effect recovery-recorded only after that boundary commits. -- Retry a child worker only when the owned process reports a fast nonzero exit before any inbound IPC. Once a hook, protocol frame, model event, or provider diagnostic exists, retrying is no longer a startup recovery and can duplicate billed or effectful work. -- In an Electron UtilityProcess bootstrap catch, `process.exitCode = 1` does not guarantee settlement because the parent IPC handle can keep the process alive. Flush the bounded stderr marker, then force a nonzero exit on a short fallback timer so main can classify and retry the pre-ready failure. -- Run `oxfmt` only on deliberately reformatted files or isolated hunks: the repository baseline contains legacy formatting, so formatting an otherwise small change can create hundreds of unrelated lines of churn. -- "Safer" compaction checks can be compatibility bugs. Before changing Aiden's Pi adapter, pin the exact upstream commit, hash the relevant sources, and mirror its tests and acceptance behavior—including empty/length summaries and usage-anchor skips—instead of preserving stricter local validators that upstream does not have. -- An Electron E2E teardown deadline must exceed the app's sequential bounded shutdown phases. A 10-second fixture timeout can kill and report a healthy process while foreground, subagent, and packaged-soak drains are still inside their documented 6s + 5s + 5s ceilings. -- Image generation can return a baked checkerboard or an opaque/RGB file even when asked for transparent onboarding art. Inspect the generated pixels, dimensions, and alpha channel before copying it into `renderer/assets/onboarding/`; extract the real background and resample only after visual inspection. -- GitHub release create/edit requests can return HTTP 503 after committing server-side state. Publication must re-read the exact tag, target SHA, draft state, and asset set before retrying or reconciling; never treat an unavailable lookup as a missing release. -- A physical XCTest transport spike can keep secrets out of the project and scheme: use a private temporary Derived Data directory, create an injected `.xctestrun` copy beside its `Build/Products` payload, inject an ephemeral canonical pairing-bootstrap JSON into that copy, then use `test-without-building`. Xcode still requires the physical device to remain unlocked through preflight and launch. -- A copied `.xctestrun` resolves `__TESTROOT__` relative to its own location. Keep the injected copy beside `Build/Products` (or deliberately rewrite every relative product path), and derive the advertised LAN address from the default-route interface instead of assuming Wi-Fi is `en0`; otherwise Xcode reports a missing test product or the phone silently times out against a link-local adapter. -- Simulator networking does not prove iOS Local Network privacy readiness. A direct physical LAN request fails as `Local network prohibited` when the host app omits `NSLocalNetworkUsageDescription`; lock both that key and the canonical `NSBonjourServices` value with an XCTest that inspects the built application bundle. -- A newly configured Tailscale Serve HTTPS handler can accept TCP before its tailnet certificate is locally available. Verify/request the node certificate through Tailscale's own CLI, retry the exact path-scoped Serve URL, then remove only that path with matching `--https` and `--set-path ... off`; never use `serve reset` as cleanup. -- Before the first handler exists, `tailscale serve status --json` can be `{}` even when tailnet HTTPS is enabled. Validate the exact normalized node DNS name against `tailscale status --json`'s certificate domains; do not require a pre-existing `TCP.443.HTTPS` listener, and still reject an explicitly incompatible 443 listener. -- Tailscale `--set-path` strips the mounted public prefix before reverse proxying. An Aiden `/api/aiden/v1` mount must target the loopback origin plus the exact same canonical API base, not the origin root; verify `/api/aiden/v1/health` through the real tailnet before claiming transport acceptance. -- `PlistBuddy Add ... string ` can strip JSON quoting when injecting a physical-test pairing payload. Use `plutil -replace ... -string`, compare the injected value's byte count/digest without printing it, and make every secret-bearing xctestrun and payload file owner-only. -- A self-signed `CA:FALSE` TLS leaf cannot be used as an Apple Security trust anchor, and an overlong server-leaf lifetime can fail Apple SSL policy even under a private anchor. Generate an installation-local CA, sign a short-lived `CA:FALSE`/server-auth leaf, present the full chain, anchor only the CA, and pin the leaf SPKI. -- A newly connected physical iPhone can remain an ineligible Xcode destination after pairing until Developer Mode is enabled, the reboot confirmation is accepted, and the phone is unlocked again. Re-read the CoreDevice/Xcode destination list before rebuilding; stale destination errors do not imply a signing failure. -- Foundation and JavaScript do not resolve duplicate JSON object keys the same way. For cross-platform security envelopes, scan raw UTF-8 JSON and reject duplicate keys—including escaped-equivalent names—before either `JSONDecoder` or `JSON.parse`; validating only the decoded object is too late. -- Cross-platform `maxLength` and date parsing need executable shared vectors: Swift `String.count` measures grapheme clusters while OpenAPI/JavaScript limits are Unicode-code-point based, and `ISO8601DateFormatter` accepts forms a strict RFC 3339 parser rejects. Use Unicode scalars for wire bounds and validate the complete timestamp grammar/calendar before constructing `Date`. -- A TTL alone is unsafe when idempotency state is pruned using wall time: after a forward jump and persisted prune, a rollback can make the same key look reusable. Persist a last-observed clock high-water mark with the ledger snapshot and fail closed for new keys until wall time advances beyond it; unresolved in-flight entries still never expire locally. -- Do not retain an unversioned idempotency-array migration path after adding a persisted clock high-water mark: even an empty legacy array is ambiguous and can reopen a pruned operation after rollback. Use an omitted snapshot only for a genuinely fresh ledger and reject every persisted shape that cannot carry the high-water value. -- WHATWG `URL` parsing removes raw and percent-encoded dot segments before exposing `pathname`. Security-sensitive canonical endpoint checks must compare the exact raw path before constructing `URL`, then apply the normal scheme, authority, query, fragment, and normalized-path checks. -- URL libraries also normalize authority bytes: controls may be stripped, Unicode hosts may become punycode, numeric hosts may change form, and padded ports lose their spelling. Cross-platform pairing identity needs a shared conservative raw authority grammar before either Foundation or WHATWG parsing, with the original endpoint string retained for equality checks. -- A `structuredClone`-safe value is not necessarily durable JSON: nested `undefined`, non-finite numbers, `-0`, sparse arrays, accessors, cycles, and nonplain objects can disappear or change. Idempotency results must be recursively validated/cloned into an exact JSON value and tested through `snapshot -> JSON.stringify/parse -> restore -> replay`. -- Entry-count limits alone do not bound durable state. Apply recursive shape limits plus per-result and aggregate serialized-byte budgets before accepting terminal replay data, and give live operation-owner registries an owner-checked terminal release path so the capacity ceiling does not become permanent exhaustion. -- Rork `asc` enables pseudonymous command telemetry by default; use `ASC_TELEMETRY_DISABLED=1` for Aiden operations and `--strict-auth` so stored profiles cannot silently mix with environment credentials. A zero-result app query is only evidence for the active key's scope, not proof of account-wide absence; check product-owned public TestFlight links and reconcile them through the correct account before creating a record. Initial app creation is now `asc web apps create`, requires an authenticated Apple web session, and must remain an explicit owner action. -- `NavigationSplitView` selection can highlight a row without pushing detail on a compact iPhone. Branch explicitly by horizontal size class: use a value-driven `NavigationStack` on compact layouts, retain the split view on regular layouts, and reconcile both selection and path across CRUD and size-class changes. -- Never convert a post-action serialization or snapshot-budget failure into a TTL-bound rejection. The external mutation may already have happened; retain its stable operation reference as an unexpiring unknown/in-flight record until authoritative reconciliation proves a terminal outcome. -- Dependency-injected application services must keep Electron-backed singleton imports type-only. Bind real stores, logging, and platform services in a separate `*-main.ts` module; otherwise focused Node tests load Electron before the pure service can be exercised. -- Web Search existing-auth consent must read Pi's persisted credential store directly. `Models.getAuth()` is not a status-only operation: it can resolve ambient environment credentials and refresh OAuth. Keep the binding service platform-free for tests and bind its owner-only `DataStore`/Pi singletons in `web-search-auth-reuse-main.ts`. -- Local Swift caches should use their own symmetric encoder/decoder rather than the stricter network RFC 3339 decoder. A plain `JSONEncoder` persists `Date` numerically by default, so decoding that file with the wire decoder fails even though the cache is valid. -- Canonical workspace paths can differ textually on macOS (`/var` versus `/private/var`). Managed-worktree authorization must compare the shared realpath-resolved environment identity, not a raw temporary-directory spelling. -- macOS LibreSSL does not support OpenSSL's `-copy_extensions` certificate flag. For an installation-local server leaf, write a bounded temporary extension file with the reviewed SAN/key-usage values and pass it through `-extfile` instead of assuming GNU/OpenSSL CLI parity. -- A private LAN CA cannot satisfy normal iOS server trust from an SPKI fingerprint alone. Keep the leaf pin and carry the installation CA certificate in the locally displayed, versioned pairing envelope so the client can anchor that exact CA before applying hostname, validity, usage, and pin checks. -- A folder-browser selection is not safe merely because its opaque nonce is one-use. Revalidate the approved-root policy, canonical directory identity, and duplicate-workspace state inside the same serialized application-service commit that persists registration; otherwise a root removal or filesystem replacement can win between token consumption and save. -- Durable idempotency must persist the in-flight admission before invoking a workspace mutation and persist the terminal result afterward. A crash between those writes should fail closed as an unknown/in-flight operation rather than allow the same key to execute twice. -- A committed chat append and a started generation are separate durability boundaries. If provider setup fails after the append, return the accepted message with a terminal error stream; if append persistence has an indeterminate outcome, keep the idempotency entry in flight until authoritative reconciliation rather than allowing a duplicate prompt. -- Resumable token streams can produce many events faster than durable storage should be written. Coalesce journal snapshots while preserving monotonic in-memory sequence order, settle the latest snapshot during quit, and close only the revoked device's live responses; an SSE disconnect alone must not cancel server-owned generation. -- An Xcode App Intents localization file reference is not a resource by itself. Adding a missing `AppShortcuts.xcstrings` reference to Copy Bundle Resources makes metadata extraction fail at build-input validation; either provide the real catalog or leave the absent reference out of the shipping resources so extraction and shortcut training use the declared intent phrases. -- After an iOS target rewrite, imported source files can remain in the project navigator without belonging to the shipping target. Verify the active `PBXSourcesBuildPhase` before trusting or testing a configuration/UI file, and make CI compile the renamed scheme for generic hardware when simulator use is prohibited. -- Xcode can also retain unlinked Swift package references and stale `Package.resolved` pins after an imported target is narrowed. Audit the actual target dependency graph, remove unused project package/product references, then keep the resolved pins and bundled third-party notices under the same regression gate. -- `await Activity.update` does not guarantee `activity.content` has already advanced. A Live Activity manager that reduces the next rapid event from that public rendered snapshot can lose semantic transitions. Keep canonical state actor-isolated in the app process, hydrate only when adopting a persisted activity, and test rapid updates on physical hardware before immediate cleanup. -- Separate `xcodebuild test-without-building` invocations normally reinstall an app-hosted XCTest bundle, which removes its Live Activities and invalidates a relaunch-persistence proof. Build and install once, then set `UseDestinationArtifacts` with the destination-relative test bundle for the second phase; verify the first host is gone, keep a cleanup phase, and validate that the CoreDevice UUID and Xcode UDID describe the same physical device. -- A physical XCTest run can pass every test and still print a `devicectl diagnose` collection error while Xcode archives partial diagnostics. Use the XCTest summary and final `TEST EXECUTE SUCCEEDED` result as the gate; treat diagnostic collection as a separate tooling warning rather than a test failure. -- A pairing window can be closed or replaced while durable device issuance is awaiting storage. Rechecking only after issuance is too late because a hidden credential may already exist; pass an exact-session authorization fence into the serialized durable mutation and check it immediately before commit. -- Re-pairing into one fixed Keychain scope makes registry rollback non-atomic because the old credential has already been overwritten. Write each device credential to a versioned scope, durably move the installation-registry pointer, and only then best-effort remove the prior scope. -- CoreDevice and `xcodebuild` can identify the same physical iPhone with different UUIDs. Use `devicectl list devices` only to confirm presence and unlock state, then resolve the actual Xcode destination identifier from `xcodebuild -showdestinations`; passing the CoreDevice UUID directly can report that an otherwise connected phone is unavailable. -- Capability-vocabulary negotiation is not authority. Persist a client's explicit support marker separately from its grants, expose server inventory only to clients that negotiated the additive vocabulary, and require both server support and the exact device grant before enabling the feature. -- Authorizing a classified resource through its full payload reader can leak reconciliation, deletion, or storage state before capability denial. Classify from bounded main-owned metadata first, normalize missing/failed classification at the outer resource boundary, and only then enter payload or effectful services. -- Rewriting a hand-formatted OpenAPI document through a whole-file JSON formatter creates thousands of unrelated diff lines. Preserve its established formatting and make schema changes as narrow patches unless a dedicated formatting migration is intentional. -- A signed high-water file does not prevent rollback when its signing key and authority state live in the same backup domain. Keep the authoritative Bot policy head in an independent macOS Keychain item; use the filesystem head only as a two-phase crash journal, and test restoration of the state, journal, and local key together. -- A durable mutation can become visible before its final lifecycle checkpoint is acknowledged. Reconcile that exact pending operation in the live application service and return the proven committed object; if exact reconciliation fails, fence further mutations until restart instead of letting a retry mint a duplicate identity or chat. -- A capability catalog should not reuse an inventory built for a narrower authority lane. The subagent MCP inventory deliberately excludes stdio and caps servers/tools below the Bot contract; give Bots a fresh inspector over ordinary MCP transports plus their own durable resource and credential incarnations. -- A durable external route and its Bot backing chat live in separate stores, so binding first leaves a crash gap. Reconcile enabled routes under the Bot mutation gate before starting the transport: create only the exact persisted chat id in the managed home, validate existing chat/policy ownership, and durably disable anything that cannot be proven or repaired. -- A soft-disabled external route can be re-enabled by restoring only its older registry file. Bind the complete normalized state digest to an independently protected Keychain generation, but do not simply write either side first: publish `pending(previous,next)`, write the file, then commit `next`; startup may reconcile only an exact previous or next digest. A separate one-way bootstrap marker must prevent anchor loss from accepting the restored file as a fresh baseline. -- Profile reset/delete must invalidate an active Bot bind before waiting for its serialized profile lane, then durably unbind routes before clearing pairing or profile state. Reversing that order can leave an enabled route behind after a partial reset failure. -- Tool-name filtering cannot make an unrestricted shell honor scoped Files access: `run_command` can follow absolute paths, `..`, symlinks, and child-process behavior outside its cwd. Until execution is confined by a sound OS boundary, withhold Custom shell for scoped/off Files and expose it only with the exact Full Mac grant; keep a fresh authority/home/catalog check immediately before every published tool effect. -- A Bot managed-home workspace is intentionally absent from `configStore`, so generic inbound-file storage can mistake its opaque workspace id for a stale ordinary workspace and fall back to a global inbox. Carry the validated Bot identity alongside the backing workspace id, resolve and canonicalize the exact managed home again at storage time, and never allow a Bot-bound failure to enter the ordinary fallback path. -- Ordinary coding tools intentionally allow lazy workspace creation, so their root guard does not pin an inode at assembly. Exact Bot file grants need the same tool factories behind a pinned-root builder; otherwise replacing the entire approved directory between schema publication and execution can silently retarget every routed tool. -- Rebuilding a Bot capability catalog before each effect detects skill drift but cannot interrupt an already-running turn at edit time. Give active Bot turns a separate inventory-generation lease, fence every controlled config/credential publication on both sides, and watch only the exact discovered `SKILL.md` directories admitted into runtime so an external edit aborts immediately without broadly observing the user’s home folder. -- Resource credentials and process credentials intentionally use different fingerprint domains. Join a child MCP process to its grant through a fresh, durable resource/credential incarnation identity while retaining the process fingerprint for execution checks; direct hash equality makes the production join impossible. -- A publication fence is only useful after the new snapshot is synchronously visible to warm readers. Publish the in-memory and disk-cache view first, then advance the inventory generation so a post-fence lease cannot observe stale authority. -- `O_NOFOLLOW` on a Node file open protects only the final component. Replacement-safe writes beneath a mutable Bot home need a native helper that pins the home and every parent with `openat`/`mkdirat`, creates the leaf relative to retained descriptors, and revalidates authority after the write. -- External Bot surfaces must admit the exact protected Bot, chat, audience, provider, and model before appending the user message or consuming a one-shot attachment. Revalidating only at provider dispatch leaves unauthorized durable input behind even when generation is denied. -- A Bot avatar upload needs both bounded container preflight and a real decode/re-encode boundary. Header inspection limits decompression work and rejects MIME confusion; only the independently decoded, center-cropped 512 × 512 PNG may enter the canonical store. -- Once an atomic avatar-manifest rename succeeds, a later directory-fsync error is not a safe rollback signal: deleting the newly named asset would leave a committed manifest dangling. Treat rename as the live publication boundary and let restart validation classify crash durability. -- Do not acquire a paired-device mutation lease before reading a bounded request body. A stalled sender would make revocation wait on attacker-controlled I/O; read and validate the bounded envelope first, then authenticate/acquire immediately before application-service admission. -- Bot capability provider/model IDs are audience-safe opaque selectors, not runtime provider IDs. Resolve them against one fresh main-only catalog snapshot and pass only the exact source identities into chat creation; never persist the opaque selector as runtime configuration or return private catalog resources over Remote. -- A mutation can pass an inventory fence, stage durable Bot policy state, and still publish after the inventory changes. Thread the same `isCurrent` predicate through the final cache-and-disk publication callback so invalidation rejects before either representation becomes visible. -- Favorite ordering spans multiple Bot identities plus one shared ordered list. Acquire Bot mutation gates in stable sorted order before the single process-wide favorites lane, and make desktop archive removal use that same lane, or simultaneous archive/reorder operations can deadlock or resurrect an archived favorite. -- Clearing a submitted mobile draft must be generation-aware: the user can type message B while message A is awaiting acceptance, so A may clear only the exact draft generation it submitted and failures must merge A with the newer text and attachments. -- Durable mobile navigation is not restorable from a chat ID alone. Scope presentation state by the exact installation and paired-device identity, then re-fetch the canonical chat before restoring a path or mutation authority. -- Feature-local Remote clients must forward `credential_revoked` into the coordinator's installation purge path. Handling it only in the main connection loop leaves stale Bot caches and credentials alive after a direct editor or chat request. -- A Bot cache keyed only by Mac instance can expose data from an older phone pairing after re-pair. Include the device identity in its directory, activation token, snapshot validation, and every stale-publication fence. -- Archived Bots may remain identity owners for readable archived conversations even when creation and favorites list only active Bots. Fetch/cache the complete Bot identity projection for inbox validation, then filter archived identities only at actionable controls. -- SwiftUI profile and inbox refreshes can overlap initial tasks, pull-to-refresh, sheet dismissal, and mutations. Fence every assignment and error with a per-load generation, invalidate in-flight loads before mutation, and scope persisted split selection by the exact installation plus paired-device identity. -- A lost create response is still ambiguous when the server returned malformed/truncated success or a mismatched success identity. Retain the exact idempotency key across network, cancellation, invalid-response, 2xx, 408, 429, and 5xx outcomes; unlock an editable draft only after a definite rejection. -- Image Playground's temporary result may be larger than the canonical upload even when it is valid. Bound the system-source admission separately from the normalized output, downsample before full decode, and enforce the smaller transport cap only after metadata-free re-encoding. -- A synchronous copy of a system completion URL creates crash residue before async normalization owns it. Remove only app-owned candidate names at process launch and retry when the editor becomes available because complete file protection can make launch-time cleanup temporarily inaccessible. -- A SwiftUI view hidden with opacity remains mounted and continues running `.task` work. A rollout flag must gate every feature ingress—including cache activation, search, retained-path restoration, deep-link presentation, and mutations—not only navigation or hit testing. -- A per-row canonical-image query can turn a bounded roster into unbounded IPC and renderer memory. Gate roster loads by viewport visibility, cap concurrent reads and retained decoded-byte estimates, prioritize selected surfaces, and ensure an evicted row re-requests when it re-enters the viewport. -- `/usr/bin/security add-generic-password -w` does not reliably consume piped stdin when launched without a controlling terminal; it can print a password prompt and hang until the bounded process timeout. Use `security -i` with a strictly tokenized command and hex value carried only on stdin, then verify the stored value through an independent read. -- Blocking a custom `URLProtocol` handler to stage overlapping responses can serialize the loading queue and make an iOS test look like an app watchdog crash. Defer response delivery without blocking the protocol callback, then release the saved protocol instance after the newer request completes. -- A Messages-like Bot surface becomes ambiguous if a Bot can own multiple writable chats. Treat the Bot identity as the chat identity at every entry point: serialize open-or-create under the Bot mutation gate, recheck after admission, project one deterministic canonical chat, and keep legacy duplicates readable but mutation-blocked. -- Repeated Bot loading can come from overlapping REST refreshes rather than excess SSE. Preserve independently valid cache segments, publish warm state immediately, reuse the active `URLSession`, and reserve animated skeletons for true cold layout loads. -- A provider's generic generation failure can hide a retired saved model. Keep the Bot chat's saved provider/model authoritative, classify the bounded provider diagnostic server-side, and direct the person to Bot Access without exposing raw provider output or silently substituting another model. -- A feature-specific provider catalog can silently diverge from a working chat setup if it reads the portable custom-provider store instead of Aiden's canonical configured-provider inventory. Exercise New Bot against a real built-in provider on a paired physical iPhone, and fence the full durable-to-memory catalog publication so post-refresh leases cannot see stale authority. -- A Full Access Bot policy that omits provider/model leaves the composer or chat metadata as accidental authority. Persist an audience-safe selection plus its exact private binding in the Bot policy, revision and fence every change, atomically rebase only the canonical Custom chat reduction without widening its other grants, and treat chat provider/model fields as a crash-recoverable execution mirror rather than the source of truth. -- A large shared SwiftUI chat body can hit the compiler's type-checking ceiling when a Bot-specific presentation is added inline. Keep the runtime shared, but split transcript, message rows, composer, toolbar, and sheets into bounded view builders so workspace and Bot chrome remain independently readable and compilable. -- Clearing a canonical Bot photo in `onDisappear` defeats both the immutable revision contract and SwiftUI view reuse: ordinary navigation or reconstruction briefly falls back to the semantic avatar and decodes the same bytes again. Keep a bounded decoded-image cache keyed by installation, paired device, Bot, and asset revision; retain the current image across connection-only refreshes and invalidate only when that exact key changes. -- Pi may persist a refreshed OAuth credential during auth resolution. Resolve expired built-in Bot auth before acquiring its inventory lease, then re-read and pin the fresh auth inside the admitted lane; refreshing only after admission invalidates the request's own lease and causes a one-time first-turn failure. -- A Bot configuration save can race a process-wide runtime inventory mutation from provider credentials, MCP configuration, or skill content. Never swallow `BotRuntimeInventoryLeaseInvalidError`: retry the complete snapshot/bind/write transaction under a fresh lease with a small bound, and fail closed without publishing if the inventory keeps changing. -- Bot catalog snapshots embed live facts: credential probes and resource incarnations can legitimately advance their revision after a client loads the editor. Rebase the stale client request onto one fresh audience-scoped snapshot, revalidate every opaque selection against that exact snapshot, and persist only its revision; do not add a second unleased read that can itself race. -- When a capability ships on remote/iOS first (e.g., Edit Bot owning model selection), audit the Mac renderer for the same surface before declaring the feature done. The Mac editor had no access section, no catalog IPC, and no updateBotAccess path; desktop-created bots silently got a main-chosen default model. -- A Remote stream projection reset is followed by a cumulative replacement, not an append-only delta. Reconcile durable chat and clear any feature-local ephemeral accumulator before consuming that replacement; otherwise each reconnect/reset can duplicate the same assistant progress even when the provider emitted every sentence only once. Keep final-answer projection separate from disclosure-only progress so copy, accessibility, completed, and streaming paths agree. -- A successful mobile image upload does not prove the saved model can see it. Project the configured model's image-input capability to the client and revalidate pending attachment kinds before consuming their one-shot handles or appending the turn; otherwise stale or incorrect runtime metadata can silently downgrade an image to a text-only request and prompt misleading filesystem exploration. -- A Compose screen nested inside a parent `Scaffold` can inherit safe-drawing insets a second time, creating a large unexplained gap below the parent's app bar. Make the product shell the single system-inset owner and set nested list scaffolds to `WindowInsets(0, 0, 0, 0)`; likewise, do not add an IME inset when `adjustResize` has already moved the window above the keyboard. -- When more than one adb transport exposes the same Pixel, always select the physical USB serial explicitly for install, launch, UI dump, and screenshots. This avoids deploying to a stale wireless transport or reading UI state from a different device connection. -- A MockWebServer cancellation test for a never-ending SSE response should throttle a response body instead of relying on `setBodyDelay`: delaying only the start can leave the queued body alive and make server shutdown wait even after the production OkHttp call was correctly cancelled. -- Gradle `connectedDebugAndroidTest` can uninstall the debug target package at the end of its managed lifecycle, deleting its app-private Remote pairing credential and caches. For a paired physical device, either use a disposable application ID/device or manually install the already-built target/test APKs and run `am instrument`; always verify pairing state and reinstall the final target afterward. -- A Compose feature screen can miss an in-place pairing transition when its only load trigger is view-owned and navigation immediately changes. Let the lifecycle ViewModel observe the authenticated client plus `CONNECTED` state directly, claim its single-flight marker before launching, and never translate a missing authoritative snapshot into a valid empty account. Server request logs are the fastest way to distinguish “route returned empty” from “route was never called.” -- `windowSoftInputMode` declared on `` does not make a Compose activity an IME-resize owner; a physical device can still resolve it to `adjust=pan` and cover bottom controls. Put the policy on the exact activity, choose one owner (`adjustNothing` plus consumed Compose IME/navigation insets for edge-to-edge), and compare composer bounds against the real IME frame on-device. -- A bounded raw-audio limit is not the HTTP JSON limit: 60 seconds of 16 kHz PCM16 is 1,920,000 raw bytes but 2,560,000 base64 bytes before envelope overhead. Derive and test both boundaries together or the advertised final seconds fail with 413. -- Speech-recognition callbacks can arrive after cancellation/destruction. Fence native callbacks, Mac preparation, capture, and transcription with one monotonically increasing session generation; lifecycle stop must invalidate it before releasing the microphone so an old completion cannot write into a newer draft. -- Codex non-login shells may not inherit either Java or Android SDK discovery even when Android Studio and the SDK are installed. For Gradle verification, point `JAVA_HOME` at Android Studio’s bundled JBR and `ANDROID_HOME` at the configured SDK; do not add machine-local `local.properties` to the repository. -- React Doctor can fail to recognize uncommitted changes in a linked Git worktree and silently fall back to a full-repository scan, even with `--scope changed`. Confirm its scope banner, inspect findings in the actual changed files, and rely on focused tests plus direct diff review rather than treating unrelated full-scan diagnostics as regressions. -- Generic iOS `build-for-testing` can stall in asset-catalog processing in this repository even when the changed Swift sources compile. For a source/test compilation gate, disable signing and exclude `*.xcassets` plus `AppIcon.icon`; keep physical-device behavior and the real signed asset/package build as separate release acceptance. -- A production-profile Playwright run is useful for exercising the packaged policy branch without signing, but it is not evidence about a signed `.app` bundle. Name the gate accurately and keep signed distribution plus physical-device termination APIs as release-environment acceptance rather than silently treating them as local passes. -- When several long-lived mobile branches overlap, do not merge their histories blindly into a release PR. Preserve unique detached commits first, start from a clean `main` worktree, squash the reviewed mobile baseline, then cherry-pick only independently scoped follow-ups; resolve documentation by combining current facts instead of reviving stale build records. -- Electron `utilityProcess.fork` script arguments are delivered to the Node service but may be absent from the packaged macOS Helper command line. Authenticate the loaded worker over its private IPC channel, and reserve `ps` PID/start identity for compare-before-signal cleanup; a smoke that forks the worker directly does not cover production launcher admission. -- A read-only status surface must not merge two independent external snapshots. Tailscale node identity, Serve state, and route classification now come from one bounded command pair; a redundant second inspection can turn one transient CLI failure into a false “Unavailable” state even when the first snapshot was healthy. -- Onboarding dismissal spans a renderer compatibility marker and a main-owned outcome. An explicit provider skip must persist `deferred`, clear any stale selected-provider identity, and teach both launch visibility and the Settings re-entry path that `deferred` is intentionally dismissed but never provider-ready. -- A Tailscale setup failure can originate before Tailscale: production and development Aiden profiles may share a persisted Remote Access port pair. Check the live listeners and per-profile `aiden-remote-v1.json` files first; preserve fail-closed startup and expose relocation only as a confirmed action that cannot orphan an owned or pending Serve route. -- Pi's provider `Context` is structurally typed, so `AgentTool` values with `execute` callbacks can reach a field declared as provider-only `Tool[]`; the agent loop can likewise spread lifecycle callbacks into provider options. Before Electron UtilityProcess IPC, positively project provider-facing tool definitions and documented stream options, then normalize the complete frame to the strict JSON wire contract. -- Generative UI guest HTML is untrusted. Keep iframe `sandbox` at `allow-scripts` only and deny guest `connect-src`. Do not use `iframe srcDoc` in the privileged renderer: Chromium inherits the parent CSP, so guest inline scripts and `aiden-genui:` host libraries never run unless parent `script-src` is widened (forbidden). Serve wrapped HTML from `aiden-genui://preview/` with CSP as a response header and point the iframe `src` at that URL. Host Chart.js/Plotly/KaTeX must load from the allowlisted exact `aiden-genui://` library names or be inlined on export—never from a CDN, and never via a scheme-wide `aiden-genui:` script-src. `protocol.registerSchemesAsPrivileged` has to run before `app.whenReady`. -- A streamed artifact card flickers when its React identity conflates content with position. Keying the card list by content hash made every same-title replace remount the whole chrome, and clearing the iframe `src` before the replacement resolved flashed a placeholder through a fixed-height slot. Key by the stable `mediaId`, fetch first and swap `src` in place, and during the persisted-message reveal window let the live streaming card win while the persisted copy stays hidden so the transition is one atomic swap instead of unmount/remount in two frames. -- An in-chat "Thinking" shimmer can die while the sidebar spinner keeps spinning because the two key off different truths: the sidebar uses stream ownership, the transcript uses content shapes (`!content`, `streamingText.length`). Dropping pi-ai's `toolcall_*` assistant events made the model's longest phase (writing tool-call arguments, e.g. a whole HTML artifact) invisible for every provider; Codex is worst because it often streams no reasoning summaries at all. Consume `toolcall_start` (the partial block carries the final `toolCall.id`, so execution events upgrade the same step), derive the ReasoningBlock's active state from an open thinking step rather than `!content`, and gate "Responding…" on recent text deltas so stale prose cannot pin a static row. -- Sequential `lstat` checks cannot secure a multi-component path against a rename between checks; each accepted ancestor has to remain pinned while the next component opens. For workspace artifact reads, extend the native descriptor-relative helper and test a deterministic mid-walk directory swap instead of relying on timing-sensitive JavaScript races. -- Moving an existing iframe or one of its ancestors between DOM parents can reload its document in Chromium even though React preserves the component identity. Positioning the unchanged host over a portaled modal also fails when transcript `isolate`/`mask-image` stacking contexts trap it below the opaque portal. Promote the unchanged host with the Popover API into Chromium's top layer, override the UA's closed-popover `display:none` plus fixed geometry for its inline state, and browser-test stacking, one-frame count, mutable guest state, small viewports, and Escape relayed from the exact sandbox window. -- A mobile verification shell may know the Android SDK through `android info` while Gradle still lacks both Java and `ANDROID_HOME`. On this workstation, use Android Studio's bundled JBR as `JAVA_HOME` and the SDK path reported by `android info` as `ANDROID_HOME`; do not write a developer-specific `local.properties` into the repository. -- An unscoped desktop chat list includes reserved or stale workspace records in addition to user workspaces. Build a workspace-ID whitelist first and project chats through it; filtering only `botId` is insufficient because the reserved Assistant home and removed-workspace orphans are not Bot chats. -- A unified mobile chat outline can amplify an old transport cost without changing the endpoint: the current global home read carries complete transcripts. Keep the first UI delivery on the compatible read, measure real payload/decode/memory, and treat a bounded paginated summary endpoint as a coordinated server+iOS+Android follow-up rather than silently adding a second background fetch. -- A generic-hardware `xcodebuild build-for-testing` can finish compiling Swift app/test sources and then remain silent in a stuck `ibtoold` finalization pass. Distinguish that local Xcode tooling hang from a Swift compile failure, retain the generic `platform=iOS` destination (never substitute a simulator), and let the clean CI hardware-target compile provide the terminal gate. -- A dictation stop can arrive before microphone or Live-session startup finishes, and a Live transcript can be visible before its finalization handshake succeeds. Latch stop intent by operation identity, keep one wall-clock budget across Live and batch fallback, preserve committed Live text, and ensure cancellation remains callable after audio capture disconnects. -- Release-time and live validators for the same downloaded catalog can drift even when both look strict. Keep one shared acceptance corpus that runs every payload through both validators, including optional display strings and numeric bounds, so packaging cannot emit a snapshot the runtime will reject. -- A post-merge updater should not run dependency installation or repository scripts with `contents: write`. Verify and test under read-only permissions, transfer a hashed artifact, and give only a minimal publish job write access with checkout credentials disabled and the token scoped to its final push command. -- A clean `npm ci` can leave the `electron` package installed without its `dist/Electron.app` payload even when npm reports dependency scripts enabled. Before diagnosing the macOS dev-runtime preparation step, check `node_modules/electron/dist` and rerun Electron's package install script when the payload is absent. -- Computer Use can expose an Electron popover's accessibility tree while returning no screenshot for the open native menu state. Use the fresh accessibility state to verify menu contents and supplement visual-state verification with focused source/tests when pixel capture is unavailable. -- The root working agreement requires consulting and updating `.memory/`, but this PR worktree contains no `.memory` directory or files. Treat current plans, normative protocol docs, source, and tests as the available project history, and call out the missing checkout memory instead of inventing it. -- Parallel server/iOS/Android contract work can briefly diverge on seemingly small constants such as activity vocabulary, cursor bounds, and unknown-field behavior. Freeze those values in one explicit coordinator message before client model tests hard-code them; here the final contract is `idle|active`, 512-character cursors, tolerant harmless additions, and fail-closed required/private fields. -- A cache can satisfy the wire-item bound but still block the UI when every paginated page rewrites one maximum-size aggregate on the main thread. Size the native summary cache against the full 10,000-row contract, move encode/read/fsync work off the UI executor, and commit pagination state only after durable persistence succeeds. -- A connected iPhone is not enough for XCTest acceptance when the local provisioning profile omits the app's App Group entitlement. Keep generic-device `build-for-testing` as the compile gate, report the signing boundary honestly, and rerun device metrics only after `group.sbtbiswas.AidenOnTheGo` is provisioned. -- Adding a derived field to the canonical chat-list projection can leave exact-shape resilience and shipping-source assertions stale even when focused feature tests pass. Search every full-suite assertion over that projection before the first push, and derive expected compatibility values through the production helper rather than duplicating the hash contract. -- The primary checkout can remain on a feature branch whose upstream was deleted, making an otherwise clean hotfix look detached from its delivery path. Check `git status --branch` and worktree registration before editing, then keep diagnosis and verification local unless branch or push authority is explicit. -- A large exact-context documentation patch can fail atomically on one wrapped paragraph. Re-read the numbered lines and retry with the smallest stable context instead of assuming earlier terminal wrapping matches the file. -- `npm ci` reports the aggregate advisory count, which can widen a release hotfix unnecessarily when every finding is development-only. Confirm the shipped graph with `npm audit --omit=dev` before changing dependencies; this hotfix had zero production advisories. - -## Packaged Electron consent acceptance without a shipped bypass - -Artifact-level diagnostics acceptance needs to exercise renderer ownership, -preload IPC, the main handler, and `crashReporter.start()` while a native -confirmation is waiting for input. Do not compile an environment-variable -consent bypass into customer builds. Launch the disposable packaged process -with a loopback Chromium debugging port, invoke the public action through the -real preload bridge, use System Events to click the real native button by its -exact accessibility label, and verify a post-start diagnostic event plus -`uploadToServer: false`. - -## Pi journal promotion recovery - -A promoted v4 journal may legitimately retain a `.v3-backup` after its migration -receipt is lost in a crash window. Recovery must inspect the authoritative journal -header before choosing a decoder: decode the backup as v3 and the live journal as -v4. Treating every backup-bearing path as v3 makes the next packaged restart fail -on the already-promoted v4 header. - -## Stacked release worktree setup - -An execution command cannot start with a workdir that the same command is meant -to create. Add the detached worktree from an existing checkout first, then run -stack assembly inside it. - -A fresh `npm ci` installs Playwright's package but not its Chromium binary, so -the full suite stops at Generative UI containment before assertions run. Mirror -CI with `npx playwright install chromium` before the first full local gate. - -## Hosted MCP OAuth verification - -The Dropbox origin-level protected-resource endpoint can return 429 while the -resource-path endpoint advertised by `WWW-Authenticate` succeeds. Verify hosted -MCP setup through that advertised RFC 9728 URL and the SDK's DCR redirect flow. - -## Release preflight environment - -`npm run release:preflight` intentionally fails outside the release runner when -Apple notarization credentials are absent. Treat local consumer/branding tests -as code gates and the credentialed GitHub release job as the signing gate. - -## Cross-client global settings - -Remote has feature-specific settings routes but no general settings contract. -An authoritative global preference therefore needs a narrow server-owned -endpoint; storing it only in iOS or Android would not change Mac agent behavior. - -## Worktree verification dependencies - -This worktree has no local `node_modules`; `npm run type-check` initially fails -with `tsc: command not found`, so verification needs the bundled runtime or a -dependency install before TypeScript suites can run. - -Android Gradle also does not discover the installed SDK in this worktree; -verification needs `ANDROID_HOME=/Users/sambitbiswas/Library/Android/sdk`. - -The full iOS `AidenRemoteClientTests` target currently has two unrelated -failures in chat-summary/private-child validation; focused memory tests are -needed to separate this change from that baseline noise. -- Verification initially referenced a guessed OpenAPI path; the canonical files are under `protocol/aiden-remote/v1/`. -- Focused Android tests need Android Studio's bundled JDK because this shell has no default Java runtime. -- URLProtocol request bodies can arrive through `httpBodyStream`; iOS request tests must use the existing `bodyData` helper. -- In zsh, a loop variable named `path` overwrites the executable search path; file-by-file commit scripts must use a non-reserved name. -- Parallel `xcodebuild` invocations share DerivedData and can lock `build.db`; run simulator build and test gates sequentially or isolate derived-data paths. -- New focused tests can pass locally yet be absent from `npm test`; register every new test script in the CI entry chain. -- Revision checks around settings writes need an explicit serialized lane; async read-then-write alone permits stale concurrent mutations. -- Backticks in `gh api -f body=...` are evaluated by zsh before submission; use single-quoted plain text or standard input for review replies. -- A merged feature does not auto-increment releases; bump both package manifests before merging when the current tag already exists. diff --git a/AGENTS.md b/AGENTS.md index 65b7263e..2f60811b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,7 +26,7 @@ OpenRouter benchmark insights are also manual-only. The live app may contact onl ## Papercuts -For complex workflows, record concise implementation friction in `.papercuts/troubleshooting.md` as it occurs. +Papercut notes are machine-local scratch and are intentionally ignored by Git. For complex workflows, record concise implementation friction in `.papercuts/troubleshooting.md` as it occurs, but do not commit the folder or its contents. ## Tests diff --git a/README.md b/README.md index a7fd8ec1..848cf0cd 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ I don't come from a coding background. I'd been bouncing between the coding agen ## Features - **Aiden Assistant** - press `⌘⌥A` to open a private assistant dock inside the main window. It follows the selected chat model, keeps its own local history and drafts, supports Stop, and can explain the app without receiving workspace tools or a hidden copy of the workspace. +- **Design Workspace** - open Design from the persistent sidebar and work on a durable React Flow-powered canvas of live, network-free HTML artboards and image references. Projects restore their exact canvas, history, comments, and source connection; Preview, Code, export, design-system context, reviewed multi-file actions, hash-safe Undo, and recoverable workspace handoff are built in. See the [shipped MVP](docs/plans/completed/design-workspace-plan.md) and [completed durable-project follow-on](docs/plans/completed/design-workspace-claude-alignment-plan.md). - **Command palette and shortcuts** - `⌘K` searches commands, chats, models, providers, Settings, and appearance actions. One typed command system also powers native menus, visible shortcut labels, transactional global hotkeys, and the searchable Keyboard Shortcuts editor. - **Commands and explicit skills** - type `/` at the start of the composer to search Aiden app commands, or `$` to search the active workspace's available skills. Commands reuse canonical app workflows; an explicitly selected skill is revalidated for the active workspace, applies to one accepted message, and persists only safe display provenance. - **Native Subagents** - a foreground chat can delegate up to four fresh `scout`, `planner`, or `reviewer` tasks. Children are read/search-only, inherit the approved workspace and model, stop with the parent, and appear as live chips plus an inspectable **Subagents** view in Environment. @@ -37,7 +38,7 @@ I don't come from a coding background. I'd been bouncing between the coding agen The roadmap is maintained in [the plan index](docs/plans/README.md). These bullets name only the unfinished parts of partially shipped work or features with no runtime implementation yet; they are directions, not release promises: - **Assistant tools and proactive nudges** - the private dock, shortcut, and Settings foundation ship today. The remaining work is approval-gated settings/status tools plus opt-in, rate-limited suggestions about useful app and workspace maintenance. See the [Aiden Assistant plan](docs/plans/aiden-assistant-plan.md). -- **Designer Mode** - no Designer Mode runtime exists yet. The proposed flow selects UI in a local Vite app, requests a bounded change, requires approval, and reviews the exact action diff; Phase 0 remains a go/no-go validation gate. See the [Designer Mode plan](docs/plans/designer-mode-plan.md). +- **Durable Design Projects and handoff** - named local projects persist the full canvas and expose Preview, Code, History, deterministic standalone/ZIP export, managed-worktree-first **Continue in workspace**, local design-system context, comments, bounded direct manipulation, and reviewed source changes. See the [completed Design Workspace follow-on plan](docs/plans/completed/design-workspace-claude-alignment-plan.md). - **Static-catalog overlays and provider completion** - Pi built-in discovery, encrypted credentials, provider-owned authentication, native streaming, stored dynamic catalogs, manual refresh, and voice credential lookup already ship. Remaining work includes remote overlays for otherwise-static hosted catalogs, Pi-native custom-endpoint composition, historical message provenance, scalable large-catalog recovery UX, and rollout cleanup. See the [Dynamic Model Catalog](docs/plans/dynamic-model-catalog-plan.md) and [Pi Provider Integration](docs/plans/pi-provider-integration-plan.md) plans. - **Truthful generation progress notes** - no progress-note runtime exists yet. The plan would show one temporary acknowledgement after an otherwise-silent start, using an explicitly selected on-device or verified hosted route without exposing hidden reasoning. See the [Generation Progress Notes plan](docs/plans/generation-progress-notes-plan.md). - **Long-session context and run control** - model-aware deterministic compaction already ships. Remaining work includes visible compaction activity, reconstructable structured checkpoints, durable-versus-working memory separation, queued follow-up messages, and safe mid-run redirects. See the [Compaction](docs/plans/compaction-plan.md) and [Taracodlab Learnings](docs/plans/taracodlab-learnings-plan.md) plans. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index ffde6919..a5f41ab2 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -36,6 +36,22 @@ Copyright (c) 2013-2026 Khan Academy and other contributors MIT License. See https://github.com/KaTeX/KaTeX/blob/main/LICENSE.txt +## React Flow + +`@xyflow/react` powers the spatial Design Workspace canvas. + +Copyright (c) 2019-2025 webkid GmbH + +MIT License. See https://github.com/xyflow/xyflow/blob/main/LICENSE + +## React Grab + +A minimized bundle of `react-grab/primitives` is vendored into `resources/generative-ui` for Design-only element hit testing inside sandboxed preview guests. Aiden does not initialize React Grab's full overlay or telemetry path. + +Copyright (c) 2025 Aiden Bai + +MIT License. See https://github.com/aidenybai/react-grab/blob/main/LICENSE + ## rpiv extensions Aiden's native extensions adapt interaction and state-management ideas from diff --git a/docs/architecture/design-comments-direct-edits.md b/docs/architecture/design-comments-direct-edits.md new file mode 100644 index 00000000..c166f5e7 --- /dev/null +++ b/docs/architecture/design-comments-direct-edits.md @@ -0,0 +1,86 @@ +# Design comments and bounded direct edits + +Status: Implemented. Comments, direct-edit IPC, pointer/keyboard controls, durable connected review, +prototype revision creation, and exact immutable undo are wired. + +## Authority boundary + +Comments and direct-manipulation gestures are context. They never grant repository, command, +network, Git, preview-session, or artifact-write authority. + +A comment target is durable only when all of these identities are present: + +- Design Project ID; +- artboard lineage ID; +- immutable revision media ID; +- an exact, single-match selector identity; and +- either the generated artifact content hash or a relative, source-version/range/preimage-hash + connected-source identity. + +An ephemeral React Grab selection or preview capability is insufficient and is never persisted. +When the current immutable revision or full source binding changes, the store marks the older +comment stale. Stale comments remain visible and can be resolved or reopened, but are never +silently retargeted. + +The comment store is main-owned, atomic, schema- and byte-bounded, and written with mode `0600`. +All writes use both database revision CAS and, for existing comments, comment revision CAS. +Corrupt or unsupported on-disk data makes the store unavailable rather than allowing a later +write to replace it. + +## Literal edit matrix + +The direct-edit core accepts only: + +- margin, padding, and gap spacing literals; +- width and height literals; +- enumerated alignment values; +- semantic CSS custom-property token names for color roles; +- border-radius literals; and +- bounded static plain text. + +CSS expressions, URLs, raw colors, arbitrary properties, negative values, markup-like text, +localized or dynamic text, rich text, computed classes, ambiguous selector/component matches, +and repeated literal-definition matches fail closed. Proof facts must report exactly one selector, +component, and literal definition match. + +Within one accepted gesture envelope, the proposal and undo identities are deterministic. That +gives the integration coordinator one idempotency key and one future undo record. A renderer IPC +retry is a new attended gesture with a newly minted gesture ID; it is not deduplicated against a +previous ambiguous request. + +## Origin-specific output + +Prototype edits produce a `prototype-revision-request` pinned to the base media ID and artifact +hash. The request instructs an artifact adapter to create a new immutable revision; the core never +overwrites artifact bytes. + +The main adapter re-reads the committed source, verifies its SHA-256 identity, proves one exact +`data-aiden-id` target and one literal inline definition, and derives a deterministic new media ID +from the proposal. It stages the new bytes, CAS-appends the lineage in the Design Project, appends +the chat artifact idempotently, and only then commits the staged bytes. A pre-CAS failure discards +only the exact pending row. A post-CAS interruption deliberately leaves the pending row for the +existing startup recovery path, so retries and restarts converge on one immutable revision. + +Connected-app edits produce a `designer-action-request` carrying the relative path, full source +version, exact range, preimage, and independently verified preimage hash. The core never writes +source. An integration adapter must turn that semantic literal edit into one exact replacement, +then submit it through the existing Designer Action review/apply/undo transaction. Full permission +must not bypass that review. + +The connected adapter resolves the live source-selection capability again, compares every path, +version, range, preimage, hash, and selector fact with the proposal, and parses the canonical TSX. +Only a single literal inline JSX style property or a single plain JSX text node is rewritten. The +caller must also provide a trusted source-graph proof that the enclosing component has one use; +missing or ambiguous graph evidence fails closed. The result is submitted to +`SourceDesignerActionService.propose`, so apply and undo retain the same review transaction as +every other Designer Action. + +## Intentional limitations + +The +prototype adapter intentionally supports only literal inline HTML style declarations and plain +text nodes. The connected adapter intentionally supports only literal inline JSX style objects and +plain JSX text nodes. Stylesheets, classes, spreads, expressions, component indirection, localized +text, rich children, ambiguous selectors, and repeated definitions fail closed instead of being +guessed. Color changes additionally require the main caller to resolve the token from the current +trusted design-system snapshot; renderer-reported token names are never sufficient authority. diff --git a/docs/architecture/design-handoff.md b/docs/architecture/design-handoff.md new file mode 100644 index 00000000..45dd1864 --- /dev/null +++ b/docs/architecture/design-handoff.md @@ -0,0 +1,90 @@ +# Design handoff architecture + +Status: Implemented. Coordinator, production effects, startup recovery, IPC, chat/model context, +renderer confirmation, cancellation, recovery, and project linkage are connected. + +## Boundary + +**Continue in workspace** graduates one immutable Prototype revision into an ordinary workspace task. It does not turn the Design Project into a source-writing authority. The handoff coordinator has no filesystem, Git, command, model, provider, staging, commit, push, pull-request, deployment, or application-source write API. + +The only content crossing the boundary is a parsed `DesignHandoffPacketV1`: + +- Design Project ID and compare-and-swap revision; +- immutable source bundle, lineage, and revision IDs plus its SHA-256 and byte size; +- content-addressed reference asset IDs; +- bounded, normalized design-decision summaries; and +- bounded desktop, tablet, and phone dimensions. + +The exact-key parser rejects unknown fields. In particular, there is no field for a prompt, transcript, internal project JSON, provider/model credentials, absolute path, arbitrary source bytes, tool authority, or source-write instruction. Decision text is conservatively rejected when it resembles a credential, absolute path, or serialized internal JSON. The installed packet is explicitly untrusted design context; subsequent implementation uses normal workspace chat permissions, file-tool approval, and Review. + +## Target confirmation + +The durable journal is path-free. Both target variants contain a renderer-safe four-label preview (`workspaceId`, workspace label, repository label, and branch label) and a SHA-256 digest of that exact preview. A production `verifyTarget` port must re-resolve authoritative main-process state immediately before any effect and return the same parsed target. + +The default target is an Aiden-managed worktree. The confirmation records committed `HEAD`, whether the source checkout was dirty, and the exact disclosure acknowledgment when dirty: uncommitted source-checkout changes are not included. `prepareWorkspace` must use the existing managed-worktree application service, create from that committed `HEAD`, and return matching `createdFromHead` evidence. + +An existing authorized workspace is accepted only with the exact strong-warning acknowledgment and target-preview digest. Main revalidates the workspace ID and the preview before use. This path reuses the existing workspace; it does not create or remove a worktree. + +## Journal and publication boundary + +`DesignHandoffJournalStore` owns `design-handoffs.json` under Electron `userData`. It uses `DataStore` atomic replacement, mode `0600`, a 2 MiB read ceiling, strict versioned parsing, a maximum of 128 records, compare-and-swap revisions, external reload before writes, and fail-closed corrupt/unsafe-file handling. It retains active and recoverable records; the oldest terminal record may be evicted only when the bound is reached. + +The coordinator advances these durable stages: + +```text +prepared + -> workspace-ready + -> chat-ready + -> context-ready + -> published +``` + +Every effect receives the stable operation ID and must be idempotent by that ID. A crash after an effect but before its journal checkpoint therefore repeats discovery of the same worktree, chat, context installation, or project link rather than creating another one. Illegal stage skips, identity replacement, cancellation clearing, and stale revisions are rejected. + +Project-link publication is the visible commit boundary. A publication call with an unknown outcome is reconciled with `inspectPublication(operationId)` before retry or rollback. The published linkage records the project, workspace, chat, task, and branch display identity. Publication does not grant the Prototype future workspace authority. + +## Cancellation and recovery + +Before publication, cancellation is journaled and rollback is attempted in reverse order: + +1. remove the installed handoff context; +2. remove the new chat/task; and +3. roll back the new managed workspace. + +Rollback ports also discover effects solely by operation ID. This covers cancellation or a crash between an external effect and its journal checkpoint. Each rollback returns a proof result. If any result is unknown or false, the coordinator stops destructive rollback, preserves the remaining workspace, and records a renderer-safe `recoverable` reason. It never reports the repository unchanged without proof. + +If publication is observed—or cancellation arrives after the published checkpoint—the coordinator preserves the linked workspace and records `recoverable`. Startup can call `resumeRecoverable()` to resume nonterminal records idempotently. Terminal `published` and `rolled-back` entries are not replayed. + +## Production port mapping + +The core deliberately defines injected ports. Production integration should map them as follows: + +- `verifyTarget`: authoritative config/workspace/Git-state resolution, including dirty state and committed HEAD; +- `prepareWorkspace`: existing `workspaceWorktreeApplicationService.create` for managed targets, or authoritative lookup for the explicitly acknowledged existing workspace; +- `createChat`: existing chat application service, tagged durably by handoff operation ID; +- `installUntrustedContext`: a bounded main-owned task-context record, not a hidden user prompt or source write; +- `publishProjectLink`: one compare-and-swap Design Project update that makes the task linkage visible; +- `inspectPublication`: authoritative Design Project lookup by operation ID; +- rollback ports: existing chat/worktree cleanup services plus durable proof that the operation-owned effect is absent. + +The production adapters must preserve the operation ID in their own effect records so “idempotent” is a verified property, not a coordinator assumption. They must not shell out directly; Git and worktree work stays behind existing application services. + +## Production effect integration + +`DesignHandoffEffectStore` owns a second owner-only, bounded ledger, `design-handoff-effects.json`. The coordinator journal records the cross-store state machine; this effect ledger records the operation-keyed identities needed to rediscover effects after a crash. It stores only workspace/chat/task IDs, renderer-safe labels, the parsed handoff packet, and the published linkage. It does not store repository paths, source bytes, prompts, credentials, Git capabilities, or tool authority. + +The existing managed-worktree and chat application services do not accept a caller-owned effect ID. The production adapter therefore derives a deterministic `feature/design-handoff-` branch and a visible `Design handoff · ` chat title from the operation ID. Before creating either effect it searches authoritative main-owned records for that tag. The effect ledger then binds the discovered/generated workspace and chat identities to the operation. An ambiguous discovery fails closed. + +Managed target inspection pairs the current Review snapshot with the cohesive committed Git `HEAD`. `verifyTarget` repeats that inspection immediately before any effect and requires the exact preview, dirty state, and commit the person confirmed. Worktree creation remains behind `workspaceWorktreeApplicationService.create`; its returned `createdFromHead` must equal the confirmation. A new managed handoff workspace is changed to `ask` permission before chat creation, so ordinary source writes and shell work retain Aiden's approval gates. An existing workspace retains its already-authorized permission and is never deleted by handoff rollback. + +The workspace chat is an ordinary non-Bot chat. Aiden currently has no separate durable Task entity, so the published `taskId` is the chat ID; both identities remain explicit in the linkage. The packet is installed in the main-owned effect ledger as untrusted task context, not appended as a hidden system/user prompt. `contextForChat(chatId)` adds that bounded context to the visible task and accepted model turn. + +Before context installation and again at publication, the production binding verifies the Design Project CAS revision, generated-artifact lineage/revision membership, committed source byte length and SHA-256, and the existence and project ownership of each content-addressed reference asset. Publication writes a separate project-indexed linkage in the effect ledger; it does not grant workspace authority back to the prototype or modify the project snapshot. + +`designHandoffApplicationService.initialize()` initializes the effect ledger. `reconcileAtStartup()` is an explicit startup hook that resumes every nonterminal journal independently, returning renderer-safe failures while logging private diagnostics. The service also exposes target previews, begin/cancel/resume, project links, and chat context for the main handler layer. + +## Current limitations + +Handoff creates or reuses a local workspace and an ordinary Aiden chat/task. It does not implement +hosted collaboration, deployment, pull-request creation, or automatic source writes. Recoverable +records remain explicit and can be resumed or cancelled from the owning project. diff --git a/docs/architecture/design-project-durable-storage.md b/docs/architecture/design-project-durable-storage.md new file mode 100644 index 00000000..09e471d9 --- /dev/null +++ b/docs/architecture/design-project-durable-storage.md @@ -0,0 +1,104 @@ +# ADR: Durable Design Project identity and storage + +Status: Implemented and covered by migration, restart, corruption, copy, and deletion fixtures. + +Date: 2026-09-01 + +Related plan: [Design Workspace Durable Projects and Handoff](../plans/completed/design-workspace-claude-alignment-plan.md) + +## Context + +The shipped Design Workspace is entered through a chat. Generated HTML is durable in the Generative UI artifact store, but canvas positions, viewport, selected revisions, and uploaded reference nodes are renderer state. A chat ID is therefore an incomplete user-facing identity: it cannot represent a durable canvas, an explicit connected-app relationship, or future comments and history without making the chat document authoritative for unrelated state. + +Design Project persistence also spans data with different safety properties: + +- chat messages own conversation and prompt history; +- the Generative UI store owns generated HTML bytes; +- a future content-addressed asset store will own reference-image bytes; +- authorized workspace services own connected-app source reads and writes; +- the project store should own only the arrangement and opaque references needed to reopen the canvas. + +Collapsing those bytes into one renderer-authored document would duplicate sensitive data, weaken size limits, and make deletion and copy failure-prone. + +## Decision + +### Project identity + +`DesignProjectSnapshotV1.id` is the public identity of a Design Project. `chatId` is a unique owned relationship, not the project identity. At most one project may own a chat, and ordinary chat deletion is routed through project deletion. + +The project-level `connectionState` records only whether a local app is bound: + +- `prototype-only` has no workspace binding; +- `connected` has one opaque workspace ID. + +It is a relationship fact, not an origin or authority claim, and grants no read or mutation authority. The library may derive a user-facing Prototype, Connected App, or combined filter from connection state plus canvas contents without persisting `mixed`. Every canvas node records its canonical data source independently as `generated-artifact`, `connected-app`, or `reference-asset`. Connected source access continues to require the existing workspace authorization and stale-snapshot checks. Full permission never bypasses Designer Action review. + +### Artboard lineage + +Artifact titles are display metadata and must not define history. Each generated artboard node owns: + +- a stable `lineageId`; +- an ordered, bounded `artifactMediaIds` history; +- an `activeMediaId` that must be a member of that history. + +Artifact media IDs may belong to only one lineage and one project. Rename, new revision, selection, history comparison, and export use lineage identity rather than title. Duplicate creates new node and lineage IDs while remapping every immutable artifact revision. + +Generated revisions cross three durable stores through a main-owned publication protocol. The Generative UI record stages validated `{ projectId, lineageId }` ownership beside the immutable media ID. A selected-artboard revision additionally records its exact active base media ID; a new-artboard lineage and node ID are deterministic hashes of project and media identity. The renderer never supplies or reconstructs this ownership from a title. + +During generation the record remains a candidate. Only a successfully completed terminal turn marks it eligible before the assistant-message append. An explicit user stop with partial Design output keeps the live preview open and asks the owning desktop renderer to choose **Keep draft** or **Discard** before terminal persistence: Keep crosses the same eligibility barrier as successful output, while Discard omits the descriptors and exact-deletes the staged rows. Dismissed prompts, failed turns, headless clients, and non-user cancellations (deletion, authority changes, scheduled cancellation, or shutdown) take the discard path without blocking for UI. After an eligible descriptor is durable, main commits the blob, atomically appends it to the project lineage, advances `activeMediaId`, and marks the ownership published. Interrupted or incomplete candidates are discarded during Design reconciliation and are excluded from generic HTML-artifact recovery. A selected revision uses semantic compare-and-swap: if its exact base is no longer active, publication fails without replacing the newer active revision. + +Startup recovery inspects Design-owned records before generic interrupted-artifact recovery. It publishes an eligible record only when the exact full artifact descriptor—including content ID, media ID, title, MIME type, byte size, and revision parent—is already present in a durable assistant message; an uncommitted eligible record without that proof is discarded, while an anomalous committed record remains suppressed. A cleanup write with an ambiguous result is safe because restart discards any remaining candidate or suppressed row using its persisted generation identity. A crash after project publication but before the final marker is safe because replay recognizes an already-owned media ID and never rolls a lineage back from a newer active revision. Generic recovery handles only records without Design ownership. Legacy artifact records without ownership fields remain readable, and legacy chat migration installs their conservative project ownership as described below. + +### Persistence and bounds + +Main owns `design-projects.json` under Electron `userData`. The `DataStore` writes it atomically with mode `0600`, rejects corrupt or unsupported input, detects external replacement, and refuses writes while the original file is unsafe. Renderer state is never authoritative. + +The V1 schema has exact keys and explicit ceilings for: + +- projects and store bytes; +- nodes and artifact revisions per artboard; +- reference-asset IDs; +- title characters and bytes; +- opaque ID lengths; +- finite canvas coordinates and millipixel normalization; +- per-project serialized bytes. + +The snapshot has no fields for source code, HTML, prompts, base64 data, absolute paths, capabilities, credentials, process IDs, temporary URLs, or source-selection handles. References are bounded opaque IDs using a path-ineligible alphabet. + +Every mutation is compare-and-swap against the project revision. A successful change increments the revision and writes a monotonic timestamp. A stale caller receives the current revision and cannot overwrite the newer project. + +### Migration + +Opening a legacy `/design/$chatId` route asks an injected main-owned adapter for bounded facts about that chat and its committed `design:` artifacts. Missing/deleted chats do not create projects. An unreadable artifact store or malformed facts block migration without replacing either source store. + +Legacy storage has no stable lineage fact. Migration therefore never groups by artifact title: each committed artifact becomes one conservative artboard and one explicit lineage. This may initially show multiple artboards that the old renderer grouped by same-title display text, but it cannot silently invent a false revision history. + +- project ID is a deterministic hash of the chat ID; +- node and lineage IDs are deterministic hashes of that artifact's media ID; +- the lineage history contains only that proven media ID and it is active; +- desktop positions reproduce the shipped 1,200 px artboard plus 120 px gap. + +The installation is a single atomic project-store mutation that rechecks chat ownership. Concurrent or interrupted first opens therefore converge on one project. A later migration version may merge lineages only if a legacy source gains a stable lineage identifier; titles remain display metadata. + +### Copy and deletion + +Project copy is a preparation protocol. An injected main-owned coordinator first prepares the target chat plus immutable artifact and asset copies, returning complete old-to-new ID mappings and a scoped rollback. The project row is installed only if the source revision is still current and every reference is mapped. A failed installation invokes rollback. + +Deletion is deliberately split: + +1. the store produces an exact project- and database-revision-bound cascade plan covering the chat, every artifact in every lineage, reference assets detached from the project, reference assets that become globally unreferenced, and injected comment/action IDs; +2. a future main-owned recoverable coordinator durably journals and executes cross-store deletion; +3. the project-store delete primitive removes only the project row and returns the same plan. + +The coordinator journals the exact plan before asking the store to consume it. Any project-database change invalidates the plan, including a concurrent project beginning to reference the same content-addressed asset. The primitive is not, by itself, permission to delete another store. Integration must not expose it directly to renderer IPC before the recoverable coordinator exists. + +Removing a missing reference uses the asset store's serialized snapshot guard around the project CAS. An upload already admitted to the asset writer queue therefore restores the content identity first and makes repair fail closed instead of detaching a newly available reference. + +## Consequences + +- Reload and restart can restore the same durable project state without making renderer state or chat JSON authoritative. +- Phase 2 can implement History without guessing lineage from mutable titles. +- Project connection state cannot be interpreted as write authority because canonical source and authorization remain separate. +- Artifact HTML and reference bytes remain in their purpose-built stores and can retain independent validation and recovery. +- IPC and renderer integration project these main-owned contracts into renderer-safe schemas rather than importing storage internals. +- Chat creation, artifact/asset copy, and cascade deletion run through recoverable coordinators with crash-boundary coverage. diff --git a/docs/architecture/design-project-lifecycle.md b/docs/architecture/design-project-lifecycle.md new file mode 100644 index 00000000..a3edfb38 --- /dev/null +++ b/docs/architecture/design-project-lifecycle.md @@ -0,0 +1,90 @@ +# Design Project lifecycle transactions + +Design Projects own a durable chat, immutable HTML artifact revisions, links to +content-addressed reference images, comments, and ephemeral Designer Actions. +Those records live in separate stores, so duplicate and delete are coordinated +by `design-project-lifecycle.ts` and the owner-only +`design-project-lifecycle.json` recovery journal. + +## Duplicate commit boundary + +1. Mint the target project and deterministic target chat identities. +2. Journal `preparing` before any dependent write. +3. Use `chatStore.copyVisibleHistory(...beforeInstall)` and + `generativeUiArtifactStore.prepareSelectedCopy()` so every copied artifact + byte exists before the copied chat becomes visible. +4. Commit the prepared artifact rows, rename the copied chat, and journal + `prepared`. +5. `DesignProjectStore.duplicate()` compare-and-swap publishes the project row. +6. The lifecycle wrapper clears the journal only after that call returns. + +On restart, an installed target project proves commit and preserves the copied +chat. Without that row, recovery removes the copied chat first and its artifact +bytes second. Reference images are immutable and content-addressed, so a +duplicate shares the bytes and receives its own project links. + +Callers must use the lifecycle coordinator's `duplicate()` method rather than +calling the configured project's `duplicate()` method directly; the project +store preparation port deliberately has no post-commit callback. + +## Delete commit boundary + +1. `planDelete()` captures the project/database revisions plus the exact chat, + artifact, reference-image, comment, and Designer Action identities. +2. Persist the `planned` journal record before deleting anything. +3. Compare-and-swap remove the project row. This publication is the irreversible + roll-forward boundary. +4. Remove comments and Designer Actions captured by the plan, then route the + chat through the ordinary main-owned chat deletion service so its private + subagent, staged artifact, Pi effect, compaction, and chat payload stores use + their existing recoverable deletion contract. +5. Re-read every remaining project and delete only confirmed reference-image + candidates that are still unreferenced. +6. Clear the lifecycle journal. + +If Aiden stops after step 3, startup sees the project row is absent and finishes +the cascade. If it stops before step 3, the row remains and startup removes only +the uncommitted journal record. Per-store cascade deletes accept a subset of the +captured identities for idempotent recovery, but fail closed if a new comment or +Designer Action appeared after confirmation. + +All project mutations and lifecycle recovery must share the coordinator's +process-local serialization lane. The coordinator's own duplicate/delete APIs +already enter it; update, rename, reference attachment, comment creation, and +Designer Action creation handlers use `runProjectMutation()`. This prevents an +in-process attachment or comment update from racing a captured cascade or the +final live-reference recheck used for reference-image cleanup. The durable +project database revision remains the cross-restart concurrency fence. + +## Ordinary chat deletion + +`routeChatDeletion()` resolves the authoritative project by chat ID. Ordinary +chats continue through the existing delete callback. A project-owned chat +instead raises `DesignProjectDeletionConfirmationRequiredError`, which carries +the exact bounded cascade plan. Deletion proceeds only when the caller returns +the matching project ID and project revision from that confirmation. This keeps +the sidebar chat action from silently stranding a project or bypassing the +Design library's cascade preview. + +## Main-process integration + +`design-project-store-main.ts` should construct the graph in this order: + +1. lifecycle journal; +2. delegating duplicate port and comment/action cascade planner; +3. `DesignProjectStore` configured with both ports; +4. cascade port using the project store, chat application service, comment + store, action service, and reference-asset store; +5. lifecycle coordinator; +6. `await lifecycle.recover()` after every dependent store initializes and + before Design IPC is admitted. + +The Design handlers should use coordinator `duplicate`, `previewDelete`, and +`deleteProject`. The ordinary chat removal handler should use +`routeChatDeletion`; it must return the confirmation-required payload to the +renderer instead of translating it into a generic delete failure. + +Use `createIdempotentDesignProjectChatDelete({ chats: chatStore, application: +chatApplicationService })` for the cascade's chat callback. It avoids turning a +restart after the chat payload was already removed into a false recovery +failure while still routing an existing chat through all private-store cleanup. diff --git a/docs/architecture/design-source-adapters.md b/docs/architecture/design-source-adapters.md new file mode 100644 index 00000000..da56b23f --- /dev/null +++ b/docs/architecture/design-source-adapters.md @@ -0,0 +1,123 @@ +# Design Source Adapters + +Status: Implemented for the supported Vite/React and bounded Next.js fixtures. Main-generated +source graphs, header-bound preview capability transport, HMR containment, and packaged acceptance +are complete; unsupported source shapes fail closed. + +## Boundary + +Design source adapters translate an observed preview element into a reviewable workspace source +range. They do not grant file, command, network, process, Git, or mutation authority. A selection is +usable only when main can prove all three identities at the same time: + +1. the exact runtime instance and selector; +2. the exact component identity and workspace-relative source range; +3. the current source hash from the authorized workspace. + +The renderer and preview are untrusted reporters. Main creates and validates the manifest, obtains +current file hashes through existing workspace authority, and applies changes only through Designer +Action review. + +## Source manifest and runtime-instance graph + +`design-source-graph-core.ts` defines the bounded `DesignSourceManifestV1` contract. A manifest is +hash-bound and contains only: + +- stable manifest, workspace, component, and runtime-instance IDs; +- bounded runtime selectors; +- workspace-relative file names, SHA-256 source versions, and exact source ranges; +- custom-component definition identity and runtime parent relationships. + +It deliberately excludes absolute paths, source bytes, commands, preview URLs, credentials, +capabilities, and write authority. Every object rejects unknown fields, paths reject traversal and +backslashes, arrays and serialized bytes are capped, and the manifest hash covers the canonical +body. + +`resolveDesignSourceSelection` fails closed when: + +- a runtime ID and selector do not identify the same single graph node; +- the reported component ID differs from the manifest; +- the request refers to another manifest revision; +- main does not supply a current file hash or that hash changed; +- multiple runtime nodes share one JSX render site; +- a component-definition operation would affect repeated instances. + +This is intentionally stricter than DOM-only selection. A repeated list item may have a unique DOM +selector while every item still originates from one JSX range. The core calls that ambiguous rather +than pretending a source edit affects only one rendered item. + +## Vite HTTP and HMR transport policy + +`source-preview-transport-core.ts` models transport authority as an ephemeral, immutable proof. Main +may issue a proof only for an exact `http://127.0.0.1:` origin that it just observed as +the sole resolved loopback address. `localhost`, hostnames, IPv6 aliases, default ports, URL +credentials, multiple addresses, and non-HTTP origins do not qualify. + +The proof contains explicit HTTP and WebSocket path prefixes, HTTP query-key allowlists, exact +hashed values for internal WebSocket transport parameters, and WebSocket subprotocols. A +structurally identical renderer object is not a proof; only a proof issued inside the main process is +accepted. + +Integration must use manual HTTP redirects and call the policy for the original request and every +redirect destination. It must resolve or otherwise effect-time prove the destination immediately +before each connection. Automatic redirect following is incompatible with this contract. + +HMR has two checks: + +1. authorize the proposed `ws://127.0.0.1:` target, path, query, protocol, and fresh + address observation; +2. authorize the actual upgrade headers, exact `Host`, corresponding HTTP `Origin`, WebSocket + version/key, and protocol list. + +Both HTTP and WebSocket paths reject cookies, authorization, credentials modes, unknown headers, +remote origins, hostname rebinding, port drift, encoded path separators, fragments, and unproven +paths or query keys. `source-preview-websocket-proxy.ts` applies that proof to the real upgrade: it +opens only a numeric IPv4 loopback socket, refuses upgrade redirects, validates the upstream +challenge response, reconstructs a minimal credential-free `101`, and owns every upgraded socket +through teardown. Vite begins with HMR unproven; after the proven `/@vite/client` response exposes +its bounded runtime token, main issues an exact token-hash and `vite-hmr`/`vite-ping` proof. + +## Next.js capability fixtures + +`source-preview-transport-next-adapter.ts` is a pure classifier over normalized, authorized-read +evidence. It does not inspect the filesystem, execute `package.json`, launch a process, or accept +source bytes. Separate fixture axes record: + +- App Router, Pages Router, hybrid, or absent router evidence; +- webpack, Turbopack, ambiguous, or unknown bundler evidence; +- client, server, mixed, or unknown component-boundary evidence; +- current, missing, stale, or ambiguous source-graph evidence. + +The four explicit client/current combinations—App or Pages crossed with webpack or +Turbopack—classify as supported source-selection candidates. `supported` still means HMR requires a +live loopback transport proof and edits require Designer Action review. Server, mixed, unknown, or +non-current source graphs are preview-only. Hybrid/absent routers, ambiguous/unknown bundlers, +non-`next dev` commands, and targets whose host or port is not controlled are unsupported. + +`source-preview-next-runtime-adapter.ts` supplies the authorized-read production side of that +boundary. It reads a bounded real `package.json`, detects `next dev`, chooses the documented Next 16 +Turbopack default or explicit webpack/Turbopack flags, walks non-symlink App and Pages route files, +normalizes static/dynamic route fixtures, and distinguishes App Router server pages from explicit +`use client` pages. It launches through the package manager without a shell override and appends an +exact `--hostname 127.0.0.1 --port ` pair. App and Pages projects use separate route +classifications even inside a hybrid repository; a source edit remains preview-only until a trusted +current source graph is attached. + +## Required integration sequence + +1. Build the source manifest from trusted compiler/plugin output and authorized workspace reads. +2. Pin it to the current preview session and source hashes; never accept a renderer-authored + manifest as authority. +3. Replace automatic preview redirects with manual hop handling through the transport policy. +4. Keep HMR behind both target and actual-upgrade proof, and preserve the controller teardown that + closes upgraded sockets plus escalates a stopped owned child process when graceful termination + expires. +5. Feed filesystem evidence through the bounded Next.js runtime detector into the pure classifier. + Do not execute project configuration to discover it. +6. Route resolved ranges into the existing hash-bound Designer Action transaction. Revalidate the + source hash again at review and apply time. +7. Complete packaged, navigation, orphan-process, App/Pages, webpack/Turbopack, server/client, and + repeated-instance acceptance before advertising adapter support. + +Until trusted source manifests and packaged/operator acceptance are complete, the adapters provide +live preview and HMR but must not advertise source selection as authoritative. diff --git a/docs/architecture/design-system-context.md b/docs/architecture/design-system-context.md new file mode 100644 index 00000000..3d3a156e --- /dev/null +++ b/docs/architecture/design-system-context.md @@ -0,0 +1,66 @@ +# Local design-system context boundary + +Design-system context is an explicit, read-only attachment to a Design Project. It is context for a selected model turn, never repository, command, network, Git, or write authority. + +## Data boundary + +The Phase 4 core accepts already-authorized read results. It does not scan a workspace, resolve package entry points, import modules, execute package scripts, evaluate JavaScript, load CSS through a browser, or follow symlinks. The caller must resolve the user-confirmed package/route selection and supply regular-file metadata plus pre-extracted semantic candidates. + +Two records have deliberately different visibility: + +- `DesignSystemSnapshotV1` is path-free and safe to project to a renderer or include in the exact model-context preview. It contains normalized semantic tokens, reviewed component metadata, icon metadata, and source hashes. +- `AttachedDesignSystemRecordV1` is main-only. It binds opaque source IDs and hashes to bounded workspace-relative paths so a later authorized refresh can prove whether the snapshot is current. + +Neither record stores absolute paths, source text, executable code, capabilities, commands, workspace mutation handles, credentials, or network locations. A snapshot hash covers normalized semantic content and its sorted source hashes. Refresh time and revision are outside that content identity. + +## Supported semantic surface + +Version 1 supports bounded static values only: + +- colors; +- spacing; +- typography families, size, line height, weight, and letter spacing; +- radii; +- shadows; +- an explicitly reviewed component catalog with variants and states; +- icon names, labels, styles, and tags. + +Semantic names are preserved rather than replaced with generated aliases. Arrays are sorted for deterministic serialization, duplicate names are rejected case-insensitively, and unknown keys fail closed. Dynamic expressions such as `var()`, `calc()`, `env()`, `url()`, interpolation, and executable objects are unsupported in V1. Unsupported source forms, directories, and symlinks are also rejected rather than guessed. + +## Freshness and detach + +The snapshot alone cannot claim freshness. Before exposing a snapshot as current, main compares every retained source ID, workspace-relative path, and SHA-256 hash with a new already-authorized read result. A missing, moved, added, or changed source produces `missing` or `changed`, and the current-snapshot API returns no snapshot until an explicit refresh succeeds. + +Detach increments the attachment revision and removes both the normalized snapshot and all workspace-relative provenance. It retains only the prior snapshot hash and source hashes for bounded audit correlation. A detached record cannot be refreshed implicitly; attaching again must be a new explicit user action. + +## Integration obligations + +The main/IPC wiring must: + +1. ask the user to choose and confirm an already-authorized workspace/package/route; +2. perform bounded, no-follow regular-file reads and revalidate filesystem identity at publication; +3. show the exact path-free snapshot before a user accepts a model turn; +4. refuse to label or send a stale snapshot as current; +5. persist main-only provenance under the owner-only atomic store contract; +6. ensure exports never include proprietary source files or main-only provenance. + +## Production adapter + +The production adapter is split into three main-only layers: + +- `design-system-workspace-extractor.ts` pins the canonical workspace device/inode, accepts only an explicit user-reviewed list of workspace-relative regular files, rejects symlinks in every path segment, reads through `O_NOFOLLOW` descriptors, bounds each file to 256 KiB and the selection to 512 KiB, and revalidates path, file, and workspace identity after the read. +- `design-system-snapshot-store.ts` persists at most 64 core attachment records in an 8 MiB owner-only atomic `DataStore`. Mutations are revision/CAS guarded and corrupt, schema-unsafe, or externally replaced stores fail closed without replacing the original bytes. +- `design-system-attachment-service.ts` composes explicit attach, refresh, freshness projection, and detach. Every filesystem operation requires the caller to resupply the currently authorized and reviewed workspace selection; an attachment ID is not a reusable filesystem capability. + +Version 1 extraction deliberately supports two static JSON document types rather than attempting to interpret arbitrary package code: + +- `tokens-v1` reads an exact `{ version: 1, kind: "tokens", tokens }` document with colors, spacing, typography, radii, and shadows. +- `catalog-v1` reads an exact `{ version: 1, kind: "catalog", components, icons }` document. Every component entry must include `reviewed: true`. + +Unknown keys, dynamic token values, invalid UTF-8/JSON, JavaScript/TypeScript modules, CSS evaluation, package resolution, directories, and symlinks fail closed. The renderer projection contains attachment state plus a path-free snapshot only when current hashes are proven; it never contains provenance, absolute paths, or raw document text. + +The application wiring initializes the store, exposes narrow IPC handlers, obtains workspace +identity from an authorized workspace and explicit package/route confirmation, binds the accepted +revision to the Design Project, shows the path-free model-context preview, and re-runs freshness +proof for accepted model turns. Prompt integration and output validation consume only that +proven-current projection. Exports exclude the main-only record and proprietary source documents. diff --git a/docs/architecture/designer-multifile-transactions.md b/docs/architecture/designer-multifile-transactions.md new file mode 100644 index 00000000..e018d7ab --- /dev/null +++ b/docs/architecture/designer-multifile-transactions.md @@ -0,0 +1,90 @@ +# Durable multi-file Designer Action transactions + +Status: Implemented. The production no-follow workspace adapter, durable IPC/review UI, project +authority binding, pre/post-write source-graph proof, deletion preflight, restart rollback, Apply, +and Undo are integrated. + +## Decision + +A multi-file Designer Action is one main-owned logical transaction. It is not a loop over the existing in-memory single-file action API. Before any write, main records the exact before and proposed after byte images, their SHA-256 digests, deterministic file order, and three idempotent effect identities per file: apply, rollback, and undo. + +The journal is the durable authority for recovery. A renderer may request preparation, review, Apply, or Undo, but it never owns transaction progress and cannot mark an effect complete. + +Startup recovery lists only records that already crossed a durable Apply, rollback, or Undo intent. A `prepared` record is still waiting for explicit review and is never auto-applied; a `recoverable` record is inert until a new reviewed transaction is created. + +## Bounds + +- 16 regular files per action. +- 192 KiB per before or after file image. +- 4 MiB across all before and after images in one action. +- 32 journal records, with oldest terminal records evicted before active or recoverable records. +- 20 MiB logical journal ceiling and 24 MiB physical read ceiling. +- Canonical NFC, portable, workspace-relative paths only. +- Duplicate paths and case- or NFKC-equivalent path collisions fail before inspection. + +The journal file is `source-designer-multifile-actions.json` under Electron `userData`. `DataStore` publishes it atomically with mode `0600`, reloads before writes, rejects stale compare-and-swap revisions, and refuses corrupt or unsafe backing files. + +## File authority port + +The core deliberately does not resolve filesystem paths. Its injected production port must prove all of these facts for every inspection and replacement: + +1. the workspace ID still names an authorized workspace; +2. the requested path is the same canonical workspace-relative path returned; +3. every retained ancestor and the opened target are contained under the pinned workspace root; +4. traversal and replacement use no-follow semantics and reject symbolic links; +5. the target is an existing regular file; +6. returned bytes, byte length, and SHA-256 agree; +7. replacement is atomic and compare-and-swaps the expected digest; +8. repeated calls with one effect ID are idempotent and cannot create a second effect. + +Containment booleans are necessary evidence, not authority by themselves. A production adapter must derive them from safe descriptor-based traversal or an equivalently race-resistant primitive; it must not set them after a lexical `path.resolve` check. + +## State machine + +```text +prepared + -> applying + -> applying (one file: pending -> write-intent -> verifying -> verified) + -> verifying + -> committed + -> rolling-back + -> rolling-back + -> recoverable + +rolling-back + -> rolling-back (reverse file order, exact after -> before) + -> rolled-back + -> recoverable + +committed + -> undoing + -> undoing (reverse file order, exact after -> before) + -> undone + -> recoverable +``` + +Every journal replacement increments one compare-and-swap revision. A same-stage replacement may advance exactly one file effect by exactly one phase. A stage change cannot also change file progress. File paths, byte images, and effect identities are immutable after preparation. + +The coordinator writes `write-intent` before asking the file port to replace bytes. It writes `verifying` after the port returns and `verified` only after a fresh inspection proves the exact postimage. A crash in any gap is unambiguous on resume: + +- current bytes equal the expected source image: issue or retry the idempotent effect; +- current bytes equal the expected target image: the effect crossed the crash boundary, so continue verification; +- current bytes equal neither image: preserve a conflict for review. + +A catchable adapter error is not treated as a process crash. The coordinator immediately reinspects after a failed replacement: an exact target image continues verification, an exact source image starts audited rollback, and an unavailable or third-state inspection becomes `recoverable`. Only actual process loss leaves an active intent for startup recovery. + +After every file is individually verified, Apply performs a second full postimage pass before `committed`. Undo and automatic rollback traverse the deterministic file list in reverse. + +## Conflict and rollback rules + +A stale preimage before the first intent causes no write and becomes `recoverable`. If a later file conflicts after earlier writes, the coordinator durably records the rollback cause before attempting reversal. A third-state file is never overwritten. If reversal encounters one, mutation stops, remaining files are inspected for conflict review, and the exact partial state is preserved rather than widening the race window. + +`rolled-back` and `undone` are written only after every file's original SHA-256 is freshly proven. A `rolled-back` record retains the bounded audit that explains why automatic rollback began, while active conflicts are tracked separately during reversal. If any proof is still missing, the journal remains `recoverable` with expected and observed digests and byte sizes. The record therefore never claims rollback merely because a write call returned. + +Recovery records are terminal for automatic mutation. A later review workflow must present the conflict and create a new exact transaction; it must not force-resume an ambiguous record. + +## Scope boundary + +This transaction changes only the explicitly reviewed, workspace-authorized regular files. It does not run commands, execute package code, create files, follow links, operate on directories, or acquire repository authority. It never stages, commits, resets, pushes, creates a branch or pull request, or otherwise invokes Git. + +Production integration must continue to apply the normal Designer Action permission and exact-review gate. Full permission does not bypass that review. diff --git a/docs/chatgpt-desktop-ui-inspiration.md b/docs/chatgpt-desktop-ui-inspiration.md index 831039dd..a7ffcb9e 100644 --- a/docs/chatgpt-desktop-ui-inspiration.md +++ b/docs/chatgpt-desktop-ui-inspiration.md @@ -82,23 +82,23 @@ flowchart TD ## UI element inventory -| Element | Shipped behavior | Inspiration for Aiden | -|---|---|---| -| Sidebar | Persistent, collapsible navigation with search, projects, recents, and secondary destinations. Overflow is softened with header/footer fade masks. | Keep Aiden's sidebar conversation-first. Add search, pin/archive, and soft scroll-edge masks before adding more destinations. | -| Composer | One rounded control plane combines prompt, attachments, project, permission, model, voice, and send. Context remains adjacent but visually subordinate. | Aiden already has the right structure. Tighten hierarchy so workspace/branch/location form one quiet context line and permission/model remain compact controls. | -| Project picker | Supports local projects, new remote projects, standalone chats, and changing the active project. | Preserve Aiden's simpler folder workspace model. Make changing workspace clearly create or move to a new chat when context cannot safely mutate in place. | -| Permission picker | Short current-state label opens descriptions of approval behavior. Full Access gets a consequence-focused confirmation. | High-value adaptation. Add concise descriptions and a real Full Access warning before changing scope. | -| Approval card | Inline with the conversation; includes the reason and scoped choices such as Allow once, broader allow, and Deny. | Upgrade Aiden's current Allow/Deny card to show tool, reason, and scope. Start with Allow once / Deny; add broader scopes only when the backend can enforce them. | -| Activity states | Worktree creation, environment setup, and conversation start expose running, complete, skipped, and failed states. | Replace a single vague tool-status line with compact, persistent state rows for multi-step local work. Avoid turning every tool call into a verbose log. | -| Review surface | File diffs lead directly to commit, push, branch creation, and PR creation. | Keep review beside the thread, not in a modal. Aiden implements working-tree and merge-base comparison, snapshot-checked commits, and an explicit non-force push; hosted PR creation remains separate. | -| Terminal | A thread-adjacent panel is toggled without leaving context. | Aiden already has this. Match panel easing and keep the closed drawer unmounted, as currently implemented. | -| Browser / artifact panels | Optional tabs/panels appear for browsing, annotations, images, documents, and previews. | Use the same shell pattern if Aiden adds file preview or review. Do not create a different layout primitive for every tool. | -| Model picker | A compact trigger expands to speed, power/reasoning, advanced options, and reset. | Borrow progressive disclosure, not the full control density. Aiden's provider/model picker should stay searchable and explain capability differences only when relevant. | -| Quick Chat | Lightweight side chat supports recent history, pop-out, resizing, and adding context to the active agent chat. | Useful only if Aiden later separates low-cost Q&A from workspace agents. Do not add it while both paths would behave the same. | -| Command system | New chat, search, model/project picker, permissions, terminal, review, browser, settings, and navigation all have command registrations and keyboard routes. | Add a small command palette once Aiden has enough stable actions to justify it. Reuse existing shortcuts rather than creating parallel behavior. | -| Settings | Deep configuration is grouped outside the thread, while model/project/permission stay at the point of action. | Continue Aiden's current settings split. Keep consequences and privacy boundaries in descriptions, not duplicated headings. | -| Toasts | Brief, top-offset status feedback; success and failure copy is action-specific. | Use for completed background actions and recoverable failures, never as the only record of an approval or destructive action. | -| Loading | Shimmer/skeleton treatments are used for content and generated assets; button actions still use compact progress indicators. | Use skeletons for delayed lists and model catalogs. Avoid shimmer on ordinary static labels. | +| Element | Shipped behavior | Inspiration for Aiden | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Sidebar | Persistent, collapsible navigation with search, projects, recents, and secondary destinations. Overflow is softened with header/footer fade masks. | Keep Aiden's sidebar conversation-first. Add search, pin/archive, and soft scroll-edge masks before adding more destinations. | +| Composer | One rounded control plane combines prompt, attachments, project, permission, model, voice, and send. Context remains adjacent but visually subordinate. | Aiden already has the right structure. Tighten hierarchy so workspace/branch/location form one quiet context line and permission/model remain compact controls. | +| Project picker | Supports local projects, new remote projects, standalone chats, and changing the active project. | Preserve Aiden's simpler folder workspace model. Make changing workspace clearly create or move to a new chat when context cannot safely mutate in place. | +| Permission picker | Short current-state label opens descriptions of approval behavior. Full Access gets a consequence-focused confirmation. | High-value adaptation. Add concise descriptions and a real Full Access warning before changing scope. | +| Approval card | Inline with the conversation; includes the reason and scoped choices such as Allow once, broader allow, and Deny. | Upgrade Aiden's current Allow/Deny card to show tool, reason, and scope. Start with Allow once / Deny; add broader scopes only when the backend can enforce them. | +| Activity states | Worktree creation, environment setup, and conversation start expose running, complete, skipped, and failed states. | Replace a single vague tool-status line with compact, persistent state rows for multi-step local work. Avoid turning every tool call into a verbose log. | +| Review surface | File diffs lead directly to commit, push, branch creation, and PR creation. | Keep review beside the thread, not in a modal. Aiden implements working-tree and merge-base comparison, snapshot-checked commits, and an explicit non-force push; hosted PR creation remains separate. | +| Terminal | A thread-adjacent panel is toggled without leaving context. | Aiden already has this. Match panel easing and keep the closed drawer unmounted, as currently implemented. | +| Browser / artifact panels | Optional tabs/panels appear for browsing, annotations, images, documents, and previews. | Use the same shell pattern if Aiden adds file preview or review. Do not create a different layout primitive for every tool. | +| Model picker | A compact trigger expands to speed, power/reasoning, advanced options, and reset. | Borrow progressive disclosure, not the full control density. Aiden's provider/model picker should stay searchable and explain capability differences only when relevant. | +| Quick Chat | Lightweight side chat supports recent history, pop-out, resizing, and adding context to the active agent chat. | Useful only if Aiden later separates low-cost Q&A from workspace agents. Do not add it while both paths would behave the same. | +| Command system | New chat, search, model/project picker, permissions, terminal, review, browser, settings, and navigation all have command registrations and keyboard routes. | Add a small command palette once Aiden has enough stable actions to justify it. Reuse existing shortcuts rather than creating parallel behavior. | +| Settings | Deep configuration is grouped outside the thread, while model/project/permission stay at the point of action. | Continue Aiden's current settings split. Keep consequences and privacy boundaries in descriptions, not duplicated headings. | +| Toasts | Brief, top-offset status feedback; success and failure copy is action-specific. | Use for completed background actions and recoverable failures, never as the only record of an approval or destructive action. | +| Loading | Shimmer/skeleton treatments are used for content and generated assets; button actions still use compact progress indicators. | Use skeletons for delayed lists and model catalogs. Avoid shimmer on ordinary static labels. | ## Environment sidebar state model @@ -106,93 +106,93 @@ The shipped ChatGPT/Codex renderer establishes two related but distinct surfaces ### Shell and layout states -| State | User sees | Interaction contract | -|---|---|---| -| Closed | Conversation uses the full workbench width; the toolbar toggle remains available. | Both surfaces are hidden from focus and accessibility navigation. `⌘⇧E` or the toolbar button always opens the Environment summary first. | -| Summary opening / closing | The compact card fades from 4px above and `.98` scale over `180ms`. | It does not resize, dim, or make the conversation inert. Motion is removed under Reduce Motion. | -| Environment summary open | A rounded top-right card shows working changes, local execution, the active branch, commit/push, and compare. | `Changes` deep-links into Review; the header action can open Files or comparison. Local is static until Aiden has a real second execution target; unsupported Git actions do not appear as dead rows. | -| Expanded work surface opening / closing | The right edge expands or retracts over the relaxed `300ms` panel timing. | Width, opacity, and geometry move together; the transition is flattened under Reduce Motion. | -| Review open | Review is selected in the expanded surface and the conversation stays mounted. | Tab, width, and open state persist. Focus enters the selected tab; the Summary control returns to the detached card without losing work. | -| Files open | Files is selected; its tree and editor occupy the same shell used by Review. | Switching tabs does not discard an editor draft. Review can deep-link a changed file into Files. | -| Resizing | A one-pixel boundary gains hover/focus emphasis while the user drags or uses arrow keys. | Easing is disabled during pointer drag. Arrow keys resize by 16px, Shift+Arrow by 40px, Home/End reach bounds. | -| Inline wide-window | Conversation and the expanded Review/Files surface are sibling columns. | Used only when the content region can preserve a useful conversation measure beside the saved panel width; the summary card always floats. | -| Overlay narrow-window | Expanded Review/Files becomes a right-side sheet over a dimmed, inert conversation. | Its width is bounded to the window; Escape, backdrop click, or Close dismisses it and restores focus. The compact summary remains non-modal at every width. | -| No workspace | A quiet explanation fills whichever Environment surface is open. | The user is directed to choose a local workspace; no dead tree, diff, or editor controls appear. | -| No Access | The active Environment surface explains that local file access is disabled. | It does not silently widen permission. The composer remains the place to change workspace access. | -| Loading | Shape-matched skeleton rows appear in the active view. | Static labels do not shimmer, inactive tabs do not keep polling, and controls expose disabled/loading states. | -| Recoverable error | An inline error keeps the panel and workspace context visible. | Retry is local to the failed list, diff, file, or save operation; a last good snapshot stays visible when possible. | +| State | User sees | Interaction contract | +| --------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Closed | Conversation uses the full workbench width; the toolbar toggle remains available. | Both surfaces are hidden from focus and accessibility navigation. `⌘⇧E` or the toolbar button always opens the Environment summary first. | +| Summary opening / closing | The compact card fades from 4px above and `.98` scale over `180ms`. | It does not resize, dim, or make the conversation inert. Motion is removed under Reduce Motion. | +| Environment summary open | A rounded top-right card shows working changes, local execution, the active branch, commit/push, and compare. | `Changes` deep-links into Review; the header action can open Files or comparison. Local is static until Aiden has a real second execution target; unsupported Git actions do not appear as dead rows. | +| Expanded work surface opening / closing | The right edge expands or retracts over the relaxed `300ms` panel timing. | Width, opacity, and geometry move together; the transition is flattened under Reduce Motion. | +| Review open | Review is selected in the expanded surface and the conversation stays mounted. | Tab, width, and open state persist. Focus enters the selected tab; the Summary control returns to the detached card without losing work. | +| Files open | Files is selected; its tree and editor occupy the same shell used by Review. | Switching tabs does not discard an editor draft. Review can deep-link a changed file into Files. | +| Resizing | A one-pixel boundary gains hover/focus emphasis while the user drags or uses arrow keys. | Easing is disabled during pointer drag. Arrow keys resize by 16px, Shift+Arrow by 40px, Home/End reach bounds. | +| Inline wide-window | Conversation and the expanded Review/Files surface are sibling columns. | Used only when the content region can preserve a useful conversation measure beside the saved panel width; the summary card always floats. | +| Overlay narrow-window | Expanded Review/Files becomes a right-side sheet over a dimmed, inert conversation. | Its width is bounded to the window; Escape, backdrop click, or Close dismisses it and restores focus. The compact summary remains non-modal at every width. | +| No workspace | A quiet explanation fills whichever Environment surface is open. | The user is directed to choose a local workspace; no dead tree, diff, or editor controls appear. | +| No Access | The active Environment surface explains that local file access is disabled. | It does not silently widen permission. The composer remains the place to change workspace access. | +| Loading | Shape-matched skeleton rows appear in the active view. | Static labels do not shimmer, inactive tabs do not keep polling, and controls expose disabled/loading states. | +| Recoverable error | An inline error keeps the panel and workspace context visible. | Retry is local to the failed list, diff, file, or save operation; a last good snapshot stays visible when possible. | ### Overview states -| State | Visual treatment | Notes | -|---|---|---| -| Working changes | `Changes` carries exact green additions and red deletions, aligned with tabular figures. | The accessible label also includes the changed-file count; color is never the only signal. | -| Partial line totals | Known `+ / −` totals remain visible with a binary/partial indicator. | Copy explains how many binary or otherwise uncounted files are excluded instead of presenting incomplete totals as complete. | -| Clean | A compact check and `Clean`, not `+0 −0`. | The row still opens Review so the completion state has a clear destination. | -| Changes loading | The row geometry stays fixed and only its trailing metadata becomes a skeleton. | The summary card and Review share one query snapshot rather than running contradictory polling loops. | -| Changes unavailable | A small error state replaces the trailing totals. | If a prior snapshot exists, it remains visible with a stale-warning message. | -| Local | Static `Local` with `Runs on this Mac` or `Isolated worktree`. | No chevron appears while Aiden has no second execution target. Workspace access remains visible without duplicating the composer permission control. | -| Branch ready | Current branch plus local tracking facts opens the existing branch/worktree menu. | The trigger and menu are shared with the composer rather than reimplemented. | -| Branch unavailable | Not-Git, unborn, detached, loading, checked-out-elsewhere, pending, and failure states remain explicit. | Branch mutations are blocked during generation and while the Files editor has a dirty or saving draft. | -| Commit or push | One row opens two explicit actions rather than chaining them. | Commit freezes the reviewed working-tree snapshot. Push freezes the selected local commit plus named remote and destination; completing one never silently performs the other. | -| Commit ready | `Commit changes` is available when Review has working changes. | Opening it freezes the reviewed snapshot, asks for a message, and makes `Staged changes only` versus `All current changes` explicit. Git hooks and normal signing configuration remain enabled. | -| Commit unavailable | The row remains explanatory for conflicts, detached HEAD, missing Git identity, oversized/unsupported snapshots, and nested workspaces. | Aiden never widens a nested workspace to repository scope. A changed content snapshot is rejected before staging or committing. | -| Commit running / failed | The dialog stays modal while Git runs, pins the originating workspace, and reports the command-specific failure inline. | Aiden locks the real index, builds the reviewed tree in an isolated index, and advances the branch only if its expected ref still matches. Failure leaves the real index unchanged and requires an explicit Review refresh before retry. | -| Commit outcome unknown | The dialog remains in an alert state that says Aiden could not determine whether the branch advanced; it never claims that no commit was created. | The real index stays unchanged. Refresh Review and inspect the branch before retrying so an ambiguous timeout cannot create a duplicate commit. | -| Commit complete | The dialog closes, Review/branch queries refresh, and a concise toast names the subject and branch. | Push is not implied or chained. Post-commit hook or reconciliation trouble is reported as a warning after the successfully created local commit, never as a false failure. | -| Push ready | The dialog names the remote and destination branch, with an optional upstream toggle. | Aiden freezes both the local branch identity and commit hash, then uses an explicit refspec with `--no-force`, no recursive submodule push, no implicit fetch, and normal pre-push hooks/authentication. Ahead/behind labels are explicitly last-fetched local-ref facts. | -| Push unavailable | The action explains no remote, unborn/detached branch, nested workspace, in-progress Git operation, or stale branch state. | Aiden does not widen a nested workspace into a repository-level remote mutation and does not invent a remote or destination. | -| Push running / rejected | The modal and workspace remain locked; non-fast-forward, hook, authentication, and network errors stay inline. | The remote name and destination remain editable after an explicit local-state refresh. Exact-remote reconciliation is reserved for ambiguous timeout or cancellation; an ordinary hook/auth rejection never becomes a false success. | -| Push outcome unknown | The alert directs the user to inspect the remote before retrying. | After timeout/abort Aiden uses `ls-remote` only to reconcile the exact destination ref; that is not a fetch and does not update local tracking refs. | -| Push complete | The dialog closes and a toast names `remote/branch`; upstream-setting trouble is a separate warning. | If Git confirms the exact remote ref after a timeout, the result is success-with-warning rather than a false failure. | -| Compare branch | A dedicated row opens Review in Compare mode. | Targets are exact local branches or last-fetched remote-tracking refs. No selection triggers a network fetch. | +| State | Visual treatment | Notes | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Working changes | `Changes` carries exact green additions and red deletions, aligned with tabular figures. | The accessible label also includes the changed-file count; color is never the only signal. | +| Partial line totals | Known `+ / −` totals remain visible with a binary/partial indicator. | Copy explains how many binary or otherwise uncounted files are excluded instead of presenting incomplete totals as complete. | +| Clean | A compact check and `Clean`, not `+0 −0`. | The row still opens Review so the completion state has a clear destination. | +| Changes loading | The row geometry stays fixed and only its trailing metadata becomes a skeleton. | The summary card and Review share one query snapshot rather than running contradictory polling loops. | +| Changes unavailable | A small error state replaces the trailing totals. | If a prior snapshot exists, it remains visible with a stale-warning message. | +| Local | Static `Local` with `Runs on this Mac` or `Isolated worktree`. | No chevron appears while Aiden has no second execution target. Workspace access remains visible without duplicating the composer permission control. | +| Branch ready | Current branch plus local tracking facts opens the existing branch/worktree menu. | The trigger and menu are shared with the composer rather than reimplemented. | +| Branch unavailable | Not-Git, unborn, detached, loading, checked-out-elsewhere, pending, and failure states remain explicit. | Branch mutations are blocked during generation and while the Files editor has a dirty or saving draft. | +| Commit or push | One row opens two explicit actions rather than chaining them. | Commit freezes the reviewed working-tree snapshot. Push freezes the selected local commit plus named remote and destination; completing one never silently performs the other. | +| Commit ready | `Commit changes` is available when Review has working changes. | Opening it freezes the reviewed snapshot, asks for a message, and makes `Staged changes only` versus `All current changes` explicit. Git hooks and normal signing configuration remain enabled. | +| Commit unavailable | The row remains explanatory for conflicts, detached HEAD, missing Git identity, oversized/unsupported snapshots, and nested workspaces. | Aiden never widens a nested workspace to repository scope. A changed content snapshot is rejected before staging or committing. | +| Commit running / failed | The dialog stays modal while Git runs, pins the originating workspace, and reports the command-specific failure inline. | Aiden locks the real index, builds the reviewed tree in an isolated index, and advances the branch only if its expected ref still matches. Failure leaves the real index unchanged and requires an explicit Review refresh before retry. | +| Commit outcome unknown | The dialog remains in an alert state that says Aiden could not determine whether the branch advanced; it never claims that no commit was created. | The real index stays unchanged. Refresh Review and inspect the branch before retrying so an ambiguous timeout cannot create a duplicate commit. | +| Commit complete | The dialog closes, Review/branch queries refresh, and a concise toast names the subject and branch. | Push is not implied or chained. Post-commit hook or reconciliation trouble is reported as a warning after the successfully created local commit, never as a false failure. | +| Push ready | The dialog names the remote and destination branch, with an optional upstream toggle. | Aiden freezes both the local branch identity and commit hash, then uses an explicit refspec with `--no-force`, no recursive submodule push, no implicit fetch, and normal pre-push hooks/authentication. Ahead/behind labels are explicitly last-fetched local-ref facts. | +| Push unavailable | The action explains no remote, unborn/detached branch, nested workspace, in-progress Git operation, or stale branch state. | Aiden does not widen a nested workspace into a repository-level remote mutation and does not invent a remote or destination. | +| Push running / rejected | The modal and workspace remain locked; non-fast-forward, hook, authentication, and network errors stay inline. | The remote name and destination remain editable after an explicit local-state refresh. Exact-remote reconciliation is reserved for ambiguous timeout or cancellation; an ordinary hook/auth rejection never becomes a false success. | +| Push outcome unknown | The alert directs the user to inspect the remote before retrying. | After timeout/abort Aiden uses `ls-remote` only to reconcile the exact destination ref; that is not a fetch and does not update local tracking refs. | +| Push complete | The dialog closes and a toast names `remote/branch`; upstream-setting trouble is a separate warning. | If Git confirms the exact remote ref after a timeout, the result is success-with-warning rather than a false failure. | +| Compare branch | A dedicated row opens Review in Compare mode. | Targets are exact local branches or last-fetched remote-tracking refs. No selection triggers a network fetch. | ### Review panel states -| State | Visual treatment | Notes | -|---|---|---| -| Not a Git repository | Centered explanatory empty state. | Files remains usable; Review does not imply that Git must be initialized. | -| Clean working tree | Compact success mark with “Working tree is clean.” | This is a meaningful completion state, not an empty-list failure. | -| Changes loading | File-row skeletons followed by diff-line skeletons after selection. | List and selected diff load independently. | -| Changed files | Bounded file list with status glyph, basename, parent path, and `+ / −` counts. | Selection uses the normal Aiden list state; staged and unstaged facts remain available to assistive output. | -| Modified | `M` status and a unified line diff. | A file may be staged, unstaged, or both; the combined view compares the current worktree to `HEAD`. Repository-configured external diff and text-conversion commands stay disabled while rendering. | -| Added / untracked | `A` or `?` with green additions. | Untracked text receives a synthetic `/dev/null → file` patch rather than disappearing from Review. | -| Deleted | `D` with the removed lines. | “Open file” is unavailable because no current file exists. | -| Renamed / copied | `R` or `C`, with the previous path shown before the current path. | The diff request includes both pathspecs so rename context is retained. | -| Conflicted | `U` in the destructive semantic color. | Review exposes the conflict without pretending Aiden has resolved it. | -| Binary | File stays in the change list; the viewer explains that no text diff exists. | The app never decodes arbitrary binary data into the renderer. | -| Large / truncated | A persistent notice sits above the bounded diff. | Truncation is explicit and never presented as a complete patch. | -| Diff loading / failed | The selected-file header remains stable while the body shows skeletons or Retry. | Working diffs carry the reviewed content snapshot and validate it before and after patch generation. A file that changed between status and diff reports a recoverable stale-state error instead of mixing a new patch with old counts. | -| Refresh failed after success | Last snapshot remains with a small warning strip. | Users keep their place instead of losing the entire review to a transient Git failure. | -| Changes / Compare modes | A compact nested tab switches between the working tree and branch comparison. | Both reuse the same bounded file list and unified diff viewer without replacing the conversation. | -| Compare target selection | The current branch is paired with an explicit local or “Last fetched” target. | Upstream is preferred, then the default branch, then another available ref. Remote targets are labeled as cached local facts. | -| Compare loading / failed | Target geometry stays stable while merge-base, divergence, file list, and diffs load. | Missing or moving refs, stale snapshots, and unrelated histories get recoverable errors; no fallback fetch occurs. | -| Compare identical | A success state distinguishes identical history from different history with the same merge-base tree. | `↑ahead ↓behind` remains visible so empty file differences never imply identical commits. | -| Compare diverged | Ahead/behind counts sit above merge-base-scoped files and diffs. | Files show `merge-base..HEAD`, while counts use `target...HEAD`; target-only changes are represented by behind count rather than mixed into the current-branch patch. | -| Compare binary / truncated | The existing binary and bounded-diff states apply unchanged. | Per-file requests carry expected HEAD, target, and merge-base hashes so list and patch snapshots cannot mix. | +| State | Visual treatment | Notes | +| ---------------------------- | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Not a Git repository | Centered explanatory empty state. | Files remains usable; Review does not imply that Git must be initialized. | +| Clean working tree | Compact success mark with “Working tree is clean.” | This is a meaningful completion state, not an empty-list failure. | +| Changes loading | File-row skeletons followed by diff-line skeletons after selection. | List and selected diff load independently. | +| Changed files | Bounded file list with status glyph, basename, parent path, and `+ / −` counts. | Selection uses the normal Aiden list state; staged and unstaged facts remain available to assistive output. | +| Modified | `M` status and a unified line diff. | A file may be staged, unstaged, or both; the combined view compares the current worktree to `HEAD`. Repository-configured external diff and text-conversion commands stay disabled while rendering. | +| Added / untracked | `A` or `?` with green additions. | Untracked text receives a synthetic `/dev/null → file` patch rather than disappearing from Review. | +| Deleted | `D` with the removed lines. | “Open file” is unavailable because no current file exists. | +| Renamed / copied | `R` or `C`, with the previous path shown before the current path. | The diff request includes both pathspecs so rename context is retained. | +| Conflicted | `U` in the destructive semantic color. | Review exposes the conflict without pretending Aiden has resolved it. | +| Binary | File stays in the change list; the viewer explains that no text diff exists. | The app never decodes arbitrary binary data into the renderer. | +| Large / truncated | A persistent notice sits above the bounded diff. | Truncation is explicit and never presented as a complete patch. | +| Diff loading / failed | The selected-file header remains stable while the body shows skeletons or Retry. | Working diffs carry the reviewed content snapshot and validate it before and after patch generation. A file that changed between status and diff reports a recoverable stale-state error instead of mixing a new patch with old counts. | +| Refresh failed after success | Last snapshot remains with a small warning strip. | Users keep their place instead of losing the entire review to a transient Git failure. | +| Changes / Compare modes | A compact nested tab switches between the working tree and branch comparison. | Both reuse the same bounded file list and unified diff viewer without replacing the conversation. | +| Compare target selection | The current branch is paired with an explicit local or “Last fetched” target. | Upstream is preferred, then the default branch, then another available ref. Remote targets are labeled as cached local facts. | +| Compare loading / failed | Target geometry stays stable while merge-base, divergence, file list, and diffs load. | Missing or moving refs, stale snapshots, and unrelated histories get recoverable errors; no fallback fetch occurs. | +| Compare identical | A success state distinguishes identical history from different history with the same merge-base tree. | `↑ahead ↓behind` remains visible so empty file differences never imply identical commits. | +| Compare diverged | Ahead/behind counts sit above merge-base-scoped files and diffs. | Files show `merge-base..HEAD`, while counts use `target...HEAD`; target-only changes are represented by behind count rather than mixed into the current-branch patch. | +| Compare binary / truncated | The existing binary and bounded-diff states apply unchanged. | Per-file requests carry expected HEAD, target, and merge-base hashes so list and patch snapshots cannot mix. | ### Files panel and editor states -| State | Visual treatment | Notes | -|---|---|---| -| Indexing | Indented tree-row skeletons. | Indexing is bounded to 4,000 entries, skips generated/vendor directories, and never walks an escaping symlink. | -| Tree ready | Collapsible hierarchy with directories first and a compact search field. | Search matches workspace-relative paths; safe in-workspace file symlinks are visibly marked. | -| Empty workspace | Instructional empty state. | Manual Refresh is present; no decorative suggestions are added. | -| Search has no result | “No matching files” inside the tree region. | The editor and any current draft stay intact. | -| Truncated index | Persistent footer explains the 4,000-entry boundary. | Collection is breadth-first and skips generated `.build` trees so a large early directory cannot hide ordinary root files; search truthfully states that it covers only the bounded index. | -| File loading | Editor-shaped line skeletons. | The path header stays stable so spatial context is not lost. | -| Editor ready / clean | Monospaced full-file editor, line-number gutter, optional wrap, and disabled Save. | Native textarea undo/redo, selection, scrolling, and keyboard behavior remain available. | -| Dirty | “Edited” appears and Save becomes available. | Drafts survive tab switches, moving back to the detached summary, and closing/reopening Environment. | -| Saving | “Saving…” replaces the status and Save is disabled. The editor is temporarily read-only and file navigation waits. | `⌘S` and the Save button use the same version-checked operation; its response cannot replace keystrokes or a different file selected during the save. | -| Saved | Brief inline “Saved” plus a completion toast. | The file index refreshes so size and modification metadata can reconcile. | -| Save failed | Error strip remains adjacent to the editor without erasing the draft. | The user can continue editing, retry, or deliberately reload. | -| Changed on disk | Save is refused with a specific concurrency message. | Aiden never overwrites a newer on-disk version silently. Reload requires a destructive-discard confirmation. | -| Switch file while dirty | Destructive-discard alert before selection changes. | Cancel keeps the current editor and draft exactly as-is. | -| Binary / invalid UTF-8 | File-unavailable state with the concrete reason. | Binary bytes are not coerced into replacement characters. | -| Too large / too many lines | File-unavailable state names the bounded editor limitation. | The external-editor control in the main toolbar remains the escape hatch for larger files. | -| Missing / deleted during session | Recoverable file-unavailable state and Retry. | The tree can be refreshed without discarding unrelated drafts. | -| Narrow editor detail | Below the shell's 540px width threshold, the tree becomes a list screen; selecting a file moves to the editor with a Back control. | The authoritative panel width drives this state. Focus moves to the detail header and returns to the prior file row or search field on Back; dirty Back still requires explicit discard. | -| Workspace or app lifecycle while dirty | Workspace changes, Settings navigation, Close, Quit, Reload, and Force Reload all preserve the current editor until the user decides. | Ordinary navigation requires save/discard first. Native close/reload gets a destructive-discard prompt; an in-progress save or Git mutation can only remain open until it reconciles. | +| State | Visual treatment | Notes | +| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Indexing | Indented tree-row skeletons. | Indexing is bounded to 4,000 entries, skips generated/vendor directories, and never walks an escaping symlink. | +| Tree ready | Collapsible hierarchy with directories first and a compact search field. | Search matches workspace-relative paths; safe in-workspace file symlinks are visibly marked. | +| Empty workspace | Instructional empty state. | Manual Refresh is present; no decorative suggestions are added. | +| Search has no result | “No matching files” inside the tree region. | The editor and any current draft stay intact. | +| Truncated index | Persistent footer explains the 4,000-entry boundary. | Collection is breadth-first and skips generated `.build` trees so a large early directory cannot hide ordinary root files; search truthfully states that it covers only the bounded index. | +| File loading | Editor-shaped line skeletons. | The path header stays stable so spatial context is not lost. | +| Editor ready / clean | Monospaced full-file editor, line-number gutter, optional wrap, and disabled Save. | Native textarea undo/redo, selection, scrolling, and keyboard behavior remain available. | +| Dirty | “Edited” appears and Save becomes available. | Drafts survive tab switches, moving back to the detached summary, and closing/reopening Environment. | +| Saving | “Saving…” replaces the status and Save is disabled. The editor is temporarily read-only and file navigation waits. | `⌘S` and the Save button use the same version-checked operation; its response cannot replace keystrokes or a different file selected during the save. | +| Saved | Brief inline “Saved” plus a completion toast. | The file index refreshes so size and modification metadata can reconcile. | +| Save failed | Error strip remains adjacent to the editor without erasing the draft. | The user can continue editing, retry, or deliberately reload. | +| Changed on disk | Save is refused with a specific concurrency message. | Aiden never overwrites a newer on-disk version silently. Reload requires a destructive-discard confirmation. | +| Switch file while dirty | Destructive-discard alert before selection changes. | Cancel keeps the current editor and draft exactly as-is. | +| Binary / invalid UTF-8 | File-unavailable state with the concrete reason. | Binary bytes are not coerced into replacement characters. | +| Too large / too many lines | File-unavailable state names the bounded editor limitation. | The external-editor control in the main toolbar remains the escape hatch for larger files. | +| Missing / deleted during session | Recoverable file-unavailable state and Retry. | The tree can be refreshed without discarding unrelated drafts. | +| Narrow editor detail | Below the shell's 540px width threshold, the tree becomes a list screen; selecting a file moves to the editor with a Back control. | The authoritative panel width drives this state. Focus moves to the detail header and returns to the prior file row or search field on Back; dirty Back still requires explicit discard. | +| Workspace or app lifecycle while dirty | Workspace changes, Settings navigation, Close, Quit, Reload, and Force Reload all preserve the current editor until the user decides. | Ordinary navigation requires save/discard first. Native close/reload gets a destructive-discard prompt; an in-progress save or Git mutation can only remain open until it reconciles. | ## Motion and transition inventory @@ -207,32 +207,32 @@ Core easing curves: - Snappy enter: `cubic-bezier(.23, 1, .32, 1)`. - Standard ease-out: `cubic-bezier(0, 0, .2, 1)`. -| Motion | Shipped treatment | Aiden adaptation | -|---|---|---| -| Panel open/close | `flex-grow` and `max-width` over `300ms`; transitions are disabled during drag. | Keep Aiden's `300ms` motion for expanded Review/Files only; the Environment summary uses the compact-popover motion and never changes conversation width. | -| Compact popover | Fade plus `translateY(-4px)` and `scale(.98)` to rest over `150ms`. | Use for menus and small contextual surfaces. It is quieter than a large zoom. | -| Model dropdown | Fade and `scale(.98 → 1)` over `320ms` with a short delay. | Reserve this slightly slower entrance for the model picker only; normal menus should stay near `150–200ms`. | -| Centered content swap | Enter over `260ms` from 8px lower and `.98` scale; exit over `180ms` with a smaller movement. | A good asymmetric pattern for major mode/content changes, but unnecessary for routine settings navigation. | -| Attachment/status chip | `280ms` entrance from 8px left and 4px down, with a tiny overshoot. | Borrow the directional entrance but remove the overshoot for Aiden: opacity plus `translate(-4px, 2px)` is enough. | -| Toast | Fade and 4px downward-to-rest movement around `250ms`; exit fades. | Appropriate for Aiden background completion and error notices. | -| Annotation/editor surface | Fade, 4px rise, and `.96 → 1` scale over the basic duration. | Reuse for a future review comment or inline edit surface, not for full dialogs. | -| Loading text | A slow, stepped shimmer; generated assets use low-contrast pulsing. | Use only when it communicates active generation. Freeze to a static state under reduced motion. | -| Scroll edges | Scroll-driven mask fades rather than animated shadows or separators. | Add to Aiden's sidebar and long menus; the existing composer/footer fade already points in this direction. | -| Reduced motion | Panel, chip, toast-adjacent, shimmer, editor, and specialty animations are disabled or flattened. | Keep every new motion behind `prefers-reduced-motion`. Do not replace motion with a hidden initial state. | +| Motion | Shipped treatment | Aiden adaptation | +| ------------------------- | ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Panel open/close | `flex-grow` and `max-width` over `300ms`; transitions are disabled during drag. | Keep Aiden's `300ms` motion for expanded Review/Files only; the Environment summary uses the compact-popover motion and never changes conversation width. | +| Compact popover | Fade plus `translateY(-4px)` and `scale(.98)` to rest over `150ms`. | Use for menus and small contextual surfaces. It is quieter than a large zoom. | +| Model dropdown | Fade and `scale(.98 → 1)` over `320ms` with a short delay. | Reserve this slightly slower entrance for the model picker only; normal menus should stay near `150–200ms`. | +| Centered content swap | Enter over `260ms` from 8px lower and `.98` scale; exit over `180ms` with a smaller movement. | A good asymmetric pattern for major mode/content changes, but unnecessary for routine settings navigation. | +| Attachment/status chip | `280ms` entrance from 8px left and 4px down, with a tiny overshoot. | Borrow the directional entrance but remove the overshoot for Aiden: opacity plus `translate(-4px, 2px)` is enough. | +| Toast | Fade and 4px downward-to-rest movement around `250ms`; exit fades. | Appropriate for Aiden background completion and error notices. | +| Annotation/editor surface | Fade, 4px rise, and `.96 → 1` scale over the basic duration. | Reuse for a future review comment or inline edit surface, not for full dialogs. | +| Loading text | A slow, stepped shimmer; generated assets use low-contrast pulsing. | Use only when it communicates active generation. Freeze to a static state under reduced motion. | +| Scroll edges | Scroll-driven mask fades rather than animated shadows or separators. | Add to Aiden's sidebar and long menus; the existing composer/footer fade already points in this direction. | +| Reduced motion | Panel, chip, toast-adjacent, shimmer, editor, and specialty animations are disabled or flattened. | Keep every new motion behind `prefers-reduced-motion`. Do not replace motion with a hidden initial state. | ## Shadow and interactive-state inventory The installed renderer defines a restrained elevation ladder rather than giving every control a floating shadow: -| Role | Reference recipe | Recommended Aiden treatment | -|---|---|---| -| Hairline | `0 0 0 .5px` at roughly 10% black | Optical edge for glass buttons, menus, toasts, and overlays. | -| Control rest | `0 1px 2px -1px` at roughly 8% black | Neutral/glass buttons only; flat ghost buttons receive none. | -| Control hover | `0 2px 4px -1px` at roughly 10% black | Pair with a small surface-contrast increase over `150ms`. | -| Control pressed | Compact inset `0 1px 2px` | Communicate depression without bounce, scale, or layout movement. | -| Popover | Hairline + `0 3px 7.5px` + a very low-opacity `0 0 20px` ambient shadow | Menus and approval surfaces; keep both shadow layers subtle. | -| Toast | Hairline + `0 4px 12px` near 10% black | Transient feedback only. | -| Dialog | Hairline + `0 16px 32px -8px` near 30% black | Modal interruption; strengthen black opacity in dark mode instead of increasing blur. | +| Role | Reference recipe | Recommended Aiden treatment | +| --------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | +| Hairline | `0 0 0 .5px` at roughly 10% black | Optical edge for glass buttons, menus, toasts, and overlays. | +| Control rest | `0 1px 2px -1px` at roughly 8% black | Neutral/glass buttons only; flat ghost buttons receive none. | +| Control hover | `0 2px 4px -1px` at roughly 10% black | Pair with a small surface-contrast increase over `150ms`. | +| Control pressed | Compact inset `0 1px 2px` | Communicate depression without bounce, scale, or layout movement. | +| Popover | Hairline + `0 3px 7.5px` + a very low-opacity `0 0 20px` ambient shadow | Menus and approval surfaces; keep both shadow layers subtle. | +| Toast | Hairline + `0 4px 12px` near 10% black | Transient feedback only. | +| Dialog | Hairline + `0 16px 32px -8px` near 30% black | Modal interruption; strengthen black opacity in dark mode instead of increasing blur. | State behavior: @@ -279,7 +279,7 @@ Aiden's shared dialogs currently animate from `scale(.8)` to `scale(1)` in `180m ## Patterns not to copy -- The ChatGPT / Work / Codex mode switch. Aiden has one clear product promise and should not fragment it prematurely. +- Product modes without genuinely separate tasks and navigation. Aiden now uses the deliberate Agent / Design switch because each mode owns a distinct persistent sidebar and work model; do not add more modes without the same product-level separation. - The full density of the shipped sidebar. Aiden should keep conversations and workspace navigation primary. - Particle bursts, icon spins, shakes, and specialty browser animations. They are tied to narrow features and would read as decorative in Aiden. - A `12px` blurred full-screen curtain for ordinary navigation. It is visually heavy and obscures spatial continuity. diff --git a/docs/plans/README.md b/docs/plans/README.md index 10a017e1..ece5b40c 100644 --- a/docs/plans/README.md +++ b/docs/plans/README.md @@ -12,7 +12,6 @@ This directory is the source of truth for Aiden's implementation plans. The engi | [Bot-First Aiden On The Go](bot-first-aiden-on-the-go-plan.md) | Active | Phases 0–9 are implemented. Every Bot has one persistent chat and one contact row; Favorites are a pinned placement, Bot chat reuses the shared runtime with Messages-inspired identity/bubbles and Aiden's existing composer, and New/Edit Bot exclusively own its durable model. Remote open-or-create, immediate exact-cache chat entry, optimistic favorites, shaped skeleton loading, stable photos, atomic desktop creation, fresh-inventory save retries, conflict-safe Mac/iOS draft rebasing, final-only Bot replies with expandable progress, native-or-companion image handling, and internal TestFlight build 22 are green. Eligible Apple Intelligence hardware, physical iPad, multi-device/Mac, packaged rollback, live Telegram, wider staged TestFlight, Xcode 27, accessibility, and App Store owner gates remain open. | | [Aiden Manual Pairing](aiden-manual-pairing-plan.md) | Implemented | The reviewed 100-bit setup-code path, shared one-use QR window, staged iOS activation, and adversarial coverage ship; hands-on LAN/Tailscale UI and physical-iPad acceptance remain open. | | [Compaction](compaction-plan.md) | Partial | Pi-native checkpoints, lifecycle/crash recovery, and exact audited-upstream compatibility ship; durable memory and provider-native paths remain open. | -| [Designer Mode](designer-mode-plan.md) | Planned | Phase 0 validation has not started in the runtime. | | [Dynamic Model Catalog](dynamic-model-catalog-plan.md) | Implemented | Validated pi.dev overlays, offline `0600` cache hydration, scoped setup refresh, four-hour launch refresh, force refresh, Pi metadata fallback, and Mac/iOS projection ship on pinned Pi 0.84.4. | | [Generative UI Artifacts](generative-ui-artifacts-plan.md) | Active | Phases 0–6 shipped: chat-scoped `render_artifact`, strict sandboxed preview/export hosts, verified vendored Chart.js/Plotly/KaTeX, permission-aware `/visualize`, crash-recoverable authoritative storage/copies, descriptor-relative workspace reads, one-iframe handoff/expansion, visible failure states, and route-stable Responding/Visualizing activity. Three-agent PR review findings are remediated with focused regression coverage. | | [Generation Progress Notes](generation-progress-notes-plan.md) | Planned | No implementation yet. | @@ -36,6 +35,8 @@ This directory is the source of truth for Aiden's implementation plans. The engi | Plan | Status | Completion note | | ---------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Design Workspace](completed/design-workspace-plan.md) | Complete | The first-class React Flow canvas now supports generated HTML artboards and an explicit local Vite/React app with exact proven element binding, mandatory reviewed writes, and hash-safe Apply/Undo. | +| [Design Workspace Durable Projects and Handoff](completed/design-workspace-claude-alignment-plan.md) | Complete | Phases 0–6 ship durable named projects and exact canvas restore, Preview/Code/History, deterministic offline export, recoverable workspace handoff, local design-system context, comments, bounded direct manipulation, and reviewed multi-file source actions. | | [Provider Model Visibility and Catalog Refresh](completed/provider-model-catalog-controls-plan.md) | Complete | Provider-wide visibility, explicit dual-source updates, bounded device-local models.dev metadata, native all-hidden behavior, and credential-isolated main refresh automation ship with two-review remediation. | | [Aiden Remote Multi-Instance Hardening](completed/aiden-remote-multi-instance-hardening-plan.md) | Complete | Authenticated pairing completion, multi-device/Mac isolation, transactional listeners, explicit packaged Tailscale CLI mode, exact route ownership, durable revocation, and physical-iPhone acceptance all pass. | | [Bots Mode](completed/bots-mode-plan.md) | Complete | Reusable Pi-backed bots now have first-class conversations, authoritative personas, soft archive, and exact one-to-one Telegram DM/topic control. | diff --git a/docs/plans/completed/design-workspace-claude-alignment-plan.md b/docs/plans/completed/design-workspace-claude-alignment-plan.md new file mode 100644 index 00000000..3c500039 --- /dev/null +++ b/docs/plans/completed/design-workspace-claude-alignment-plan.md @@ -0,0 +1,446 @@ +# Design Workspace Durable Projects and Handoff Plan + +Status: Complete — Phases 0–6 implemented and verified 2026-09-01 +Date: 2026-09-01 +Implementation baseline: `feature/design-workspace` at `da1104bcdf1af48eb821ff4f76956e54868dcd6d` +Predecessor: [completed Design Workspace MVP](design-workspace-plan.md) + +Research references: + +- [Claude Design getting started](https://support.claude.com/en/articles/14604416-get-started-with-claude-design) +- [Claude Design product-design workflow](https://academy.claude.com/tutorials/using-claude-design-for-prototypes-and-ux) +- [Claude Artifacts code, download, MCP, and persistence surface](https://support.claude.com/en/articles/9487310-what-are-artifacts-and-how-do-i-use-them) +- [v0 Design Mode and code workflow](https://api2.v0.dev/docs/quickstart) +- [v0 code editing](https://v0.dev/docs/code-editing) +- [Onlook source-backed visual-edit architecture](https://docs.onlook.com/developers/architecture) + +## Execution status + +- [x] Phase 0 — contract, ADR, migration fixtures, and responsive IA +- [x] Phase 1 — durable projects, assets, exact canvas restore, and lifecycle recovery +- [x] Phase 2 — Preview / Code / History, deterministic clean export, and offline acceptance +- [x] Phase 3 — recoverable Continue in workspace handoff and restart UI +- [x] Phase 4 — explicit local design-system context, freshness, prompt use, and validation +- [x] Phase 5 — durable comments, bounded direct manipulation, and immutable prototype undo +- [x] Phase 6 — source graph, durable multi-file transactions, contained adapters, onboarding, and package acceptance + +Acceptance receipt: [signed development package and operator evidence](../../testing/design-workspace-package-acceptance-2026-09-01.md) + +## Executive decision + +Keep Design Workspace, but change the next milestone from **more canvas tools** to **a durable local design project that can graduate into real code**. + +Aiden should align with the useful Claude Design product loop: + +```text +durable project + → chat and visual exploration + → inspectable code and versions + → design-system-aware refinement + → explicit engineering handoff +``` + +Aiden should not copy Claude Design's hosted product boundary. Its differentiation is: + +- local-first project and source ownership; +- the user's existing provider and model rather than a dedicated model stack; +- generated prototypes with no implicit repository, command, network, or Git authority; +- source changes that always show an exact review and require approval; +- a managed-worktree-first path from design intent to inspectable code. + +This is a priority pivot, not a product reset. The shipped generated-artifact and source-backed runtimes remain the foundation. + +## Why this follow-on exists + +The shipped UI looks like a project canvas, but its durable unit is still a chat-linked HTML artifact: + +- generated HTML survives restart in `generative-ui-artifacts.json`; +- chat messages retain artifact metadata and media IDs; +- React Flow positions, viewport, selected revision, uploaded reference nodes, and visual-edit state live only in renderer memory; +- generated source has no native Code view; +- export produces one sandboxed standalone `.html`, not a clean source bundle; +- a connected app exposes only the selected before/after range in Designer Action review; +- the Design route does not expose the normal Files or Review surfaces; +- action history and preview ownership do not survive app restart. + +That boundary is safe, but it is not yet the durable project, source visibility, and handoff experience people reasonably infer from the canvas. + +## Product contract + +### 1. Design Project becomes the durable user object + +`DesignProjectId`, not `chatId`, becomes the public identity of Design Workspace. + +Each project owns: + +- one canonical attended design conversation; +- generated artboards and immutable revisions; +- optional connection to one authorized local workspace/app; +- reference images and bounded source/design-system context; +- the saved canvas arrangement and presentation state; +- comments, decisions, and action history introduced by later phases; +- explicit export and handoff records. + +The backing chat remains reusable infrastructure, but it is no longer the product's visible storage model. + +### 2. Two origins remain explicit + +| Origin | What is canonical | Mutation rule | +| ----------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------- | +| **Prototype** | Aiden-owned immutable HTML/CSS/JS revisions | A prompt, comment, or direct edit creates a new artifact revision; it never writes a repository | +| **Connected app** | Files in the authorized local workspace | Every change becomes the same hash-bound Designer Action and requires exact review | + +The UI must visibly label the origin. A generated prototype never silently acquires workspace authority, and connecting an app never rewrites existing generated revisions. + +### 3. Align behavior, not hosting + +Match Claude Design where it improves the local workflow: + +- persistent projects and project library; +- chat plus canvas refinement; +- reference images and code/design-system context; +- inline comments; +- direct visual controls for bounded changes; +- versions and history; +- code inspection and clean export; +- explicit coding-agent/workspace handoff. + +Do not treat these as parity requirements: + +- hosted sharing links or public publishing; +- simultaneous multiplayer editing; +- organization administration and analytics; +- PDF/PPTX/Canva/partner export matrix; +- one-click deployment, domains, or a hosted application runtime; +- an Anthropic-specific MCP dependency. + +## Competitive alignment matrix + +| Product expectation | Current Aiden | Delivery decision | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------------------------- | +| Durable named project | Chat-linked artifacts only | **Immediate:** add a main-owned project store and Design library | +| Reopen exact canvas | Artifact bytes survive; layout does not | **Immediate:** persist versioned canvas snapshots | +| Preview and code | Preview plus standalone HTML export | **Immediate:** add Preview, Code, and History surfaces | +| Clean source export | One wrapper `.html` | **Immediate:** export canonical source and a deterministic ZIP bundle | +| Design conversation | Floating composer on canvas | Preserve; add a wide-layout collapsible project/conversation rail | +| Reference assets | Upload works for the current renderer session | Persist bounded assets and their canvas nodes | +| Design-system context | Not implemented | Add explicit local snapshot/import after project durability | +| Inline comments | Not implemented | Add persistent element/artboard comments after source identity is durable | +| Direct manipulation | Not implemented | Add narrow token/literal actions through the existing review transaction | +| Engineering handoff | Listed as later depth | Make **Continue in workspace** a primary milestone | +| Existing app visual edits | Narrow Vite/React path ships | Preserve and deepen after the project/code foundation | +| Sharing/deployment | Not implemented | Deliberately defer; export and local handoff come first | + +## Durable model + +### `DesignProjectSnapshotV1` + +Main owns an atomic, owner-only store under Electron `userData`. Renderer storage is never authoritative. + +```ts +interface DesignProjectSnapshotV1 { + version: 1; + id: string; + revision: number; + title: string; + chatId: string; + workspaceId?: string; + connectionState: "prototype-only" | "connected"; + createdAt: number; + updatedAt: number; + canvas: { + viewport: "desktop" | "tablet" | "phone"; + flowViewport: { x: number; y: number; zoom: number }; + nodes: Array<{ + id: string; + kind: "artboard" | "reference-image" | "source-preview"; + canonicalOrigin: "generated-artifact" | "connected-app" | "reference-asset"; + lineageId?: string; + x: number; + y: number; + artifactMediaIds?: string[]; + activeMediaId?: string; + assetId?: string; + }>; + }; + referenceAssetIds: string[]; + designSystemBinding?: { + id: string; + revision: number; + }; +} +``` + +Rules: + +- Store updates use compare-and-swap revisions and the existing atomic `DataStore` safety contract. +- Cap projects, nodes, coordinates, titles, reference assets, and serialized bytes. +- Cap and normalize coordinates before persistence; reject `NaN`, infinity, and renderer-crafted oversized snapshots. +- Do not persist preview capabilities, process IDs, source-selection handles, temporary URLs, provider credentials, prompts, or file contents in the project snapshot. +- Reference images move to a bounded content-addressed asset store; project JSON holds IDs, not repeated base64 payloads. +- Artifact HTML remains in the authoritative Generative UI store until a separately tested migration proves a better layout. +- Opening an existing design chat lazily and idempotently creates a project using its committed `design:` artifacts. +- Existing routes redirect compatibly from `/design/$chatId` to the new project identity without breaking saved links. +- Every generated artboard receives a stable lineage ID during migration. Titles are labels, never revision identity; renaming or reusing a title cannot merge histories. +- `canonicalOrigin` is required for every node and must match its kind. `lineageId` and ordered `artifactMediaIds` are required for artboards, while reference-image and source-preview nodes forbid lineage fields. +- Connection state never grants mutation authority. Each artboard retains its canonical origin, and only a connected-app artboard with a live proven source binding can propose a Designer Action. +- Project duplication copies its referenced immutable artifacts and assets through the existing crash-recoverable preparation flow. +- Project deletion previews its cascade and removes the backing chat, project snapshot, unreferenced artifact records, assets, comments, and action history as one recoverable operation. +- Ordinary chat deletion cannot silently strand or remove a Design Project; route it through the project deletion confirmation. + +## User experience target + +### Design library + +The persistent **Design** destination opens a project library with: + +- New project; +- recent projects with title, origin, update time, and artboard count; +- Prototype and Connected App filters; +- duplicate, rename, export, and delete actions; +- a clear local-storage label; +- recovery states when a project or artifact needs repair. + +Creating a project asks for one material choice: + +1. **Prototype an idea** — repository-free generated design; or +2. **Connect a local app** — select/reuse an authorized workspace and review its detected app command. + +### Project workbench + +Wide layouts use a collapsible project rail and canvas: + +- **Conversation** — prompts, decisions, and comments; +- **Canvas** — existing React Flow surface; +- **Inspector** — Preview / Code / History for the current selection. + +Compact layouts keep the canvas primary and present conversation/inspector as accessible drawers. Do not reuse the narrow Environment overlay. + +The elevated composer remains the model/context control plane. The selected provider, model, permission, and project origin remain visible and stable. + +### Code surface + +Phase 2 starts with an honest read-only view of the canonical generated document: + +- syntax-highlighted source with line numbers and find; +- Copy source; +- Save standalone HTML; +- Download source bundle; +- content hash, byte size, revision, and provenance; +- no claim that inline CSS/JS are separate files when the canonical artifact is one document. + +The deterministic source bundle initially contains: + +```text +/ + index.html + README.md + design-project.json + references/ # only explicitly included, safe assets +``` + +`README.md` records the design brief, viewport expectations, revision identity, the immutable source revision timestamp, and that the output is a prototype requiring engineering review. The mutable save/export time lives only in Aiden's local export record, outside the bundle. ZIP entries use a fixed order, timestamp, and mode so identical inputs produce identical bytes. Host libraries are inlined or included from Aiden's verified vendored copies; no CDN is introduced. + +For a connected app, Code shows the actual proven workspace file read through the existing authorized file service, plus the selected binding and current diff. Editing remains disabled until the same stale-snapshot and Designer Action boundaries can back it. + +## Delivery phases + +### Phase 0 — contract, ADR, and migration fixtures (2–4 days) + +Deliverables: + +- Write an ADR for project identity, chat ownership, artifact references, deletion, copy, export, and migration. +- Freeze `DesignProjectSnapshotV1`, IPC schemas, byte/count ceilings, and revision/CAS behavior. +- Add fixture stores for current generated-only chats, mixed generated/source projects, copied chats, deleted chats, corrupt artifacts, and interrupted migrations. +- Prototype the project library and Preview / Code / History information architecture at 390, 700, 1000, and 1280 px. +- Record terminology: **Design Project**, **Prototype**, **Connected App**, **Continue in workspace**, and **Designer Action**. + +Exit gate: + +- Every existing committed Design artifact has one deterministic migration outcome. +- Deleting, copying, or renaming cannot orphan or silently destroy artifact bytes. +- The project snapshot contains no transient capabilities, secrets, code, prompts, or absolute paths. + +### Phase 1 — durable projects, assets, and exact canvas restore (6–10 days) + +Deliverables: + +- Add the main-owned project store, project library IPC, and renderer queries. +- Migrate existing design chats lazily and idempotently. +- Persist node positions, active revisions, viewport, project title, origin, and bounded reference assets. +- Restore the exact canvas after route changes, renderer reload, and app restart. +- Add optimistic local movement with debounced revisioned persistence and explicit conflict recovery. +- Add rename, duplicate, and recoverable delete. +- Keep preview processes stopped after restart; restore only the saved configuration and require explicit Start. + +Exit gate: + +- A project with 20 artboards and 10 references reopens with the same arrangement after a forced renderer crash and full app restart. +- Stale renderer writes cannot overwrite a newer project revision. +- Artifact, project, asset, and chat cleanup passes crash-boundary tests. + +### Phase 2 — Preview / Code / History and clean export (5–8 days) + +Deliverables: + +- Add the selection inspector with Preview, Code, and History tabs. +- Show generated canonical source read-only with Copy and Save actions. +- Show connected-app source only through authorized workspace reads and stale snapshots. +- Add immutable revision history, labels, timestamps, model provenance, and comparison between two generated revisions. +- Export standalone HTML and a deterministic ZIP source bundle. +- Add **Reveal saved location** only for user-chosen exports; do not expose the internal JSON store as an editable project. + +Exit gate: + +- A user can answer where a project is saved, inspect its source, compare revisions, and export it without opening internal app data. +- Exported output runs offline, contains no credentials or absolute paths, and passes containment/package inspection. + +### Phase 3 — Continue in workspace (7–12 days) + +Deliverables: + +- Add a primary **Continue in workspace** action for a selected generated revision. +- Default to an Aiden-managed worktree created from committed `HEAD`; clearly disclose that dirty source-checkout changes are not included. +- Let the user choose an existing authorized workspace only through a stronger warning and exact target preview. +- Create a bounded handoff packet containing the selected source bundle, reference asset IDs, design decisions, responsive states, and artifact hashes. +- Open a normal workspace chat/task with that packet as untrusted design context. +- Require ordinary file-tool approvals and Review for implementation; the handoff itself never writes application source. +- Link the resulting workspace task and branch back to the Design Project without granting the prototype ongoing authority. +- Journal worktree creation, chat creation, handoff-context installation, and project-link publication under one main-owned coordinator. Before publication, cancellation rolls back when that can be proven safe; after the boundary, Aiden preserves and surfaces the recoverable managed workspace rather than claiming the source repository is unchanged. + +Exit gate: + +- The handoff can produce a clean, reviewable implementation diff without copying hidden prompts, internal JSON, credentials, or unrelated chat history. +- Canceling before worktree creation leaves the source repository unchanged. Cancellation after creation either proves rollback or preserves an explicitly recoverable managed workspace. +- Project, task, worktree, and branch identities remain explicit and recoverable. + +### Phase 4 — local design-system context (8–14 days) + +Deliverables: + +- Add explicit **Attach design system** from an authorized local workspace/package. +- Start with semantic tokens, typography, spacing, radii, shadows, icons, and a reviewed component catalog; do not execute arbitrary repository code during indexing. +- Store a bounded, versioned, path-free normalized snapshot under `userData`; retain source hashes and workspace-relative provenance in main only. +- Show exactly what will be sent to the selected model and allow detach/refresh. +- Use the snapshot in prototype prompts and validate output against named tokens/components where possible. +- Add monorepo package and route selection with explicit confirmation. + +Exit gate: + +- Refresh detects changed source and never serves a stale snapshot as current. +- A design-system attachment does not add repository write, command, network, or Git authority. +- Generated output visibly uses the selected semantic system in golden fixtures without bundling proprietary source files into exports. + +### Phase 5 — comments and bounded direct manipulation (10–16 days) + +Deliverables: + +- Add persistent comments anchored to artboard revision plus exact React Grab selector/source identity. +- Resolve, reopen, and mark stale comments when their target revision or source binding changes. +- Add direct controls only for a proven literal matrix: spacing, size, alignment, color token, radius, and static text where safe. +- Prototype-origin direct edits create a new immutable artifact revision. +- Connected-app direct edits emit the same Designer Action proposal and exact review as model-generated edits. +- Add a layers tree only after it shares the exact selection/identity contract; never create a second DOM authority. + +Exit gate: + +- One gesture maps to one revision or one reviewable action and one exact undo step. +- Dynamic/localized/rich text, computed classes, ambiguous components, and shared repeated definitions fail closed. +- Keyboard, pointer, high-contrast, reduced-motion, and compact-layout paths have focused coverage. + +### Phase 6 — source depth, adapters, and release acceptance (8–14 days) + +Deliverables: + +- Add durable multi-file Designer Actions with atomic rollback, crash recovery, and conflict review. +- Introduce a source manifest/runtime-instance graph for custom components and repeated instances. +- Add contained Vite WebSocket/HMR only after packaged orphan-process and navigation acceptance. +- Revalidate every HTTP redirect against the fixed loopback preview target before adding WebSocket/HMR proxying. +- Add Next.js behind separate App Router, Pages Router, webpack, Turbopack, server/client, and route fixtures. +- Update onboarding and the final feature-tour gallery with the durable-project and handoff mental model, including a new optimized 1024 × 1024 transparent asset if the existing tile no longer represents the product. +- Run signed/package inspection and real-client operator acceptance. + +Exit gate: + +- Supported source selections resolve correctly or explicitly fail; they never guess a file/range. +- Multi-file Apply/Undo survives app termination at every write boundary. +- A signed package starts, restores, edits, exports, hands off, and cleans up without orphaned preview processes. + +## Mobile and remote contract + +The interactive canvas remains Mac-only until a separate native design surface is approved. + +iOS and Android may receive only a bounded project projection: + +- project ID, title, origin, updated time, artboard count, and static thumbnail when available; +- an informational **Available on Mac** state for interactive preview/code/edit until a separate authenticated, consent-aware remote-open command is designed; +- no executable HTML, source paths, project JSON, comments with code snippets, preview URLs, or Designer Action payloads. + +Changes to shared chat/artifact contracts require inspection and focused tests in both native clients. Unsupported HTML continues to render an explicit Mac-only state rather than a blank card. + +## Security and privacy invariants + +- All project, artifact, asset, comment, and action stores are device-local, owner-only, bounded, schema-validated, atomic, and recoverable. +- Generated guests keep their unique-origin sandbox and network-denying CSP. +- Design-system indexing is explicit, read-only, workspace-authorized, and does not execute package code. +- Raw code or design-system context goes only to the provider/model selected by the user for that accepted turn. +- Project selection, comments, and direct manipulation are context—not authority. +- Full permission never bypasses Designer Action review. +- Handoff never stages, commits, pushes, creates a PR, deploys, or writes source automatically. +- Remote URLs, hosted shares, and partner exports do not enter the local preview allowlist. +- Internal stores are not advertised as user-editable files; export creates an explicit portable copy. + +## Verification matrix + +### Storage and migration + +- First launch, lazy legacy migration, duplicate migration, old-version read, schema rejection, corruption, unsafe file, disk full, and interrupted atomic publication. +- Concurrent canvas movements, stale CAS, rename/copy/delete races, chat deletion, artifact deletion, shared asset references, and garbage collection. +- Renderer crash, main crash, full restart, app update, and one-version rollback. + +### Project UI + +- Empty/new/recent/mixed-origin/recovery states. +- Exact canvas restore at 390, 700, 1000, and 1280 px. +- Conversation and inspector drawers, keyboard traversal, focus restoration, VoiceOver names, high contrast, and reduced motion. +- Large bounded projects without unbounded React Flow or source-render work. + +### Code and export + +- Source escaping, syntax rendering, copy, find, revision comparison, stale connected files, and unauthorized workspace access. +- Deterministic ZIP manifest, offline open, no CDN, no secrets/absolute paths, executable/symlink rejection, and export cancellation/overwrite behavior. + +### Handoff and source changes + +- Managed-worktree creation from committed `HEAD`, dirty-source disclosure, cancellation, branch identity, task linkage, and cleanup. +- Ask/Full/No Access behavior, exact before/after review, stale preimage/postimage, Deny, Apply, Undo, crash recovery, and multi-file rollback. + +### Design systems and direct edits + +- Token bounds, component-catalog bounds, refresh/staleness, detach, malicious files, symlink swaps, and no package execution. +- Exact selector/source identity, repeated instances, custom components, static/dynamic text, Tailwind literals, CSS custom properties, and ambiguous failure states. + +## Delivery priority + +1. **Now:** Phase 0 contract and Phase 1 durable projects. +2. **Next:** Phase 2 code/history/export and Phase 3 managed-worktree handoff. +3. **Then:** Phase 4 design-system context and Phase 5 bounded visual editing. +4. **After evidence:** Phase 6 broader source adapters and release acceptance. + +Do not start sharing, multiplayer, deployment, or partner exports before Phases 1–3 prove that Aiden can preserve, expose, and hand off one local Design Project reliably. + +## Definition of aligned + +Aiden is sufficiently aligned with Claude Design for its chosen local-first position when a person can: + +1. create or reopen a named Design Project; +2. see the same artboards, references, arrangement, and history after restart; +3. refine broadly through chat or narrowly through a selected element/comment; +4. inspect and copy the underlying generated source; +5. export a portable prototype bundle; +6. attach bounded context from a real local design system; +7. continue the chosen design in an isolated workspace with its intent and references intact; +8. review every repository change and undo exact accepted actions; +9. understand at every step what is stored locally, what is sent to a model, and what can mutate source. + +Hosted collaboration and one-click deployment are separate product decisions, not blockers for this alignment milestone. diff --git a/docs/plans/completed/design-workspace-plan.md b/docs/plans/completed/design-workspace-plan.md new file mode 100644 index 00000000..e40a2207 --- /dev/null +++ b/docs/plans/completed/design-workspace-plan.md @@ -0,0 +1,176 @@ +# Design Workspace Plan + +Status: Complete — generated and source-backed Design Workspace MVP shipped + +Date: 2026-08-30 + +Product references: Open Design, MagicPath interaction material supplied by the product owner, React Flow, and React Grab + +Source-backed research baseline: Aiden `b6b0eff6bb55e0113a05cc8d069fce2c1be67b40`; Onlook `423e2e924366419e418ee049093872d535eea41a` + +## Product outcome + +Design Workspace is one first-class Aiden product: an infinite canvas where a person and the existing AI backend create, inspect, and refine live interfaces. It supports app-generated, network-free HTML/CSS/JS artboards and an explicit source-backed path for a local Vite/React app with reviewed workspace writes, exact diffs, and undo. + +The two document origins share the Design sidebar entry, spatial canvas, React Grab selection language, context chips, composer, and review concepts. They do **not** share authority: + +| Document origin | Current state | Authority | +| --- | --- | --- | +| Generated design | Shipped MVP | App-owned artifacts only; no repository, command, network, or source authority | +| Source-backed app | Shipped MVP | Explicit project opt-in; main-owned preview lifecycle, exact proven source binding, always-reviewed single-file writes, action review, and exact undo | + +This is intentionally one roadmap rather than a small chat feature plus a separate Designer Mode product. “Designer mode” is the visual-edit interaction inside Design Workspace, not another sidebar destination. + +## Non-negotiable product contract + +- Design remains a full-height, canvas-first destination from the persistent sidebar. +- React Flow owns spatial artboards, images, pan, zoom, marquee selection, placement, fit controls, and accessible canvas navigation. Rendered HTML elements never become React Flow nodes. +- React Grab owns element hit-testing inside each sandboxed or instrumented document. +- Visual edit mode selects the **exact single DOM `Element` returned by React Grab**. Aiden must not promote a nested text span, icon, button child, or layout container to the nearest `data-aiden-id` ancestor. +- One exact element may be selected per artboard. Shift may preserve selections across different artboards, within the existing bounded context limit. +- An exact element with its own valid `data-aiden-id` uses that stable identity; every other element uses React Grab's exact selector as bounded, untrusted prompt context. +- Selection is context, not authority. It never by itself permits file reads, writes, shell execution, network access, source changes, Git operations, or tool escalation. +- The selected provider and model remain unchanged. Design uses the current Aiden AI backend rather than a second agent stack. +- Generated-design edits return complete immutable HTML revisions. Source-backed edits use a main-owned, hash-bound proposal and approval transaction. +- GitHub import, plugin install, pull requests, deploy, sharing, multiplayer, and hosted collaboration are not MVP requirements. + +## Shipped foundation + +### Phase 1 — focused generated-design backend — complete + +- `design: true` is an exact, main-validated attended-turn intent and cannot combine with `/visualize`. +- A positive capability allowlist leaves only the Design-owned `render_artifact` extension; coding, file, shell, web, MCP, schedules, skills, Computer Use, image generation, Telegram controls, and subagents remain unavailable. +- `render_artifact` accepts complete inline vanilla HTML/CSS/JS documents. Cross-turn results are immutable `design:` artifacts in app-owned, crash-recoverable storage. +- Main revalidates exact media IDs and content hashes, loads only app-owned HTML, caps combined context at 128 KiB, and marks prior designs and element descriptors as untrusted model reference data. +- The existing opaque-origin `sandbox="allow-scripts"` iframe, network-denying CSP, strict validator, storage quotas, export flow, and native transcript contract remain authoritative. +- The Design brief supports up to four requested screens, stable titles for revisions, distinct titles for new artboards, responsive semantics, keyboard states, and meaningful `data-aiden-id` markers. + +### Phase 2 — first-class full canvas — complete + +- `Design` appears beside Scheduled and Bots in the persistent sidebar. +- `/design` resolves an eligible ordinary chat in the active workspace and `/design/$chatId` owns the stable studio URL. +- The route replaces the conversation body with a full-height canvas while keeping the persistent app sidebar and the durable chat/provider/model owner. +- The prompt composer floats above the continuous canvas instead of sitting in a footer surface. +- Design cards deep-link to their route and revision. The Design route does not mount Environment, Terminal, a side workbench, or a compact modal. +- Empty, generating, ready, stale-preview, unavailable, desktop, tablet, phone, revision, and export states remain truthful. + +### Phase 3 — spatial HTML/CSS studio — complete + +- React Flow provides the infinite canvas, multiple grouped-revision artboards, local image-reference nodes, pan/zoom/fit, artboard selection, and placement. +- The left rail provides Select (`V`), Visual edits (`E`), Preview, New design, Upload image, and Hand (`H`); Space-drag remains available. +- Image references are local, bounded vision attachments and may be combined with selected artboards for the next prompt. +- A pinned, vendored React Grab primitives bundle runs only inside Design guests. It keeps telemetry and network access absent and is excluded from exports and ordinary artifact previews. +- The guest bridge validates the parent command and per-preview capability. The host validates the exact iframe window, capability, schema, string bounds, media ID, artifact hash, and chat ownership. +- React Grab returns exact element context: tag, label, selector, optional own `data-aiden-id`, role, and safe short text. Form values, URLs, raw source, filesystem paths, IPC, and unrestricted HTML are excluded. +- Element, artboard, and image selections appear as removable chips in the elevated composer and apply to one accepted turn. +- Same-title output creates another revision of an artboard; distinct stable titles create new artboards without losing canvas state. + +## Shipped source-backed MVP + +The following phases extend the same Design Workspace. They do not silently attach repository power to generated artboards. + +### Phase 4 — containment, lifecycle, and identity proof — complete + +- A real Vite/React fixture proves the chosen sandboxed-iframe architecture; Electron `` and remote browsing remain disabled. +- A person must explicitly start one detected root-package Vite script. Main launches direct argv with `shell: false`, assigns a loopback port, bounds logs and readiness time, scopes ownership to workspace and renderer document, and tears down the process group. +- A main-owned read-only reverse proxy strips unsafe response headers, injects the pinned React Grab bridge, and accepts only GET/HEAD requests to the owned loopback target. +- React Grab source context is treated as untrusted evidence. Main canonicalizes the workspace, resolves only supported source files, rejects ambiguous suffix matches, and verifies the exact intrinsic JSX tag before creating a binding. +- A stable `id`, `data-testid`, or `data-aiden-id` may recover an exact intrinsic JSX element only when that same tag/attribute/value match is unique in the uniquely resolved file. Otherwise selection is unsupported. +- Browser coverage proves exact nested-element binding, unchanged source before approval, one exact write, exact undo, and fail-closed handling for an unmapped child. + +### Phase 5 — source-backed read-only preview — complete + +- **Connect app** lives inside the existing full Design route and shows the exact detected command before Start. +- The running source app appears as a React Flow artboard and can coexist with generated artboards and image references. +- Preview lifecycle, script detection, capability, logs, loopback endpoint, and stop behavior are main-owned and exposed through bounded workspace/owner-scoped IPC. +- Unsupported roots, launch failures, timeouts, crashes, and stopped states remain explicit. The MVP supports root-package scripts whose command directly invokes Vite. + +### Phase 6 — exact source-backed element selection — complete + +- Visual edit mode uses React Grab for the exact single DOM element. The renderer receives a bounded descriptor; only main may turn it into a workspace path and JSX range. +- Main binds the selection to renderer owner, workspace, preview session, canonical file, exact range, source hash, and a two-hour opaque selection handle. +- Ambiguous files, unsupported extensions, custom-component-only positions, mismatched tags, missing source metadata, repeated stable selectors, and changed source fail closed. +- The source selection appears in the shared composer-chip language and excludes generated artboard/image context for the same turn. + +### Phase 7 — Designer Action foundation — complete + +- Source-backed Design generation gets one structured `propose_design_action` capability rather than general mutation tools. +- Each proposal is bound to workspace, chat, opaque selection, exact canonical file/range, source preimage hash, bounded replacement, and a plain-language label. +- Every proposal opens a mandatory floating Designer Action review with before/after source. Full workspace permission never bypasses this review. +- Apply rechecks ownership and preimage, uses the versioned workspace writer, and records the postimage hash. Undo proceeds only while that exact postimage remains on disk; external edits become an explicit stale action. +- Deny writes nothing. No Designer Action runs Git staging, commits, stash, checkout, reset, whole-tree restore, or a shell command. + +### Phase 8 — point → ask → review → apply → undo — complete + +- Selecting a proven source element adds an exact source/path chip to the elevated composer and sends only an opaque main-resolved selection handle to generation. +- The model proposes a replacement; it cannot apply it. The user reviews and chooses Apply or Deny in the canvas. +- Apply or Undo advances the source artboard revision and reloads the preview. The review remains available for exact undo after apply. +- Unit, IPC-contract, renderer-contract, vendor, and real Chromium/Vite coverage exercise the shipped flow and its failure boundaries. + +This completes the first source-backed MVP. The implementation deliberately ships a narrow, auditable path rather than claiming universal DOM-to-source editing. + +## MVP limitations carried forward + +- Root `package.json` only; no monorepo package picker, nested app discovery, route chooser, or arbitrary command entry. +- Vite/React only; no Next.js, webpack, Turbopack, Vue, Svelte, or remote URL adapter. +- One proven intrinsic JSX range and one file per Designer Action. Custom-component definitions, repeated instances, fragments, portals, shadow roots, and multi-file edits fail closed or remain preview-only. +- React Grab/sourcemap evidence plus exact tag or unique stable selector replaces a repository source transform in this slice. There is no source manifest or runtime-instance graph yet. +- Action history is scoped to the running app session. It is not a durable cross-restart ledger. +- The proxy is intentionally read-only and does not proxy Vite WebSocket HMR. Aiden forces an iframe revision reload after Apply/Undo; the dev server may still perform its own client-side HMR. +- Static Tailwind/global-CSS context, layers, property editing, direct manipulation, component insertion, and generated-to-repository handoff remain later depth. + +## Later depth + +1. Root/package/route selection for monorepos and multiple Vite apps. +2. A durable action ledger and versioned multi-file transaction with crash recovery and conflict review. +3. A source manifest/instance graph for custom components, lists, repeated definitions, fragments, portals, SVG, and open shadow roots. +4. A contained Vite WebSocket/HMR path with packaged-app and orphan-process acceptance. +5. Layers tree synchronized with exact React Grab selection. +6. Bounded style/property inspector whose changes become the same reviewed Designer Action. +7. Static text editing for proven JSX literals; dynamic/localized/rich text fails closed. +8. Design-system component insertion, safe image asset rewriting, routes/pages, and responsive state editing. +9. Direct manipulation for a narrow literal Tailwind/`className` matrix, one gesture per action/undo step. +10. Multi-select within an artboard after stale-selection and shared-definition behavior is proven. +11. Optional explicit **Build app / Continue in workspace** handoff from a generated artifact. +12. Next.js adapters only after separate App/Pages Router, webpack/Turbopack, and server/client fixture gates. +13. GitHub/IDE handoff, pull-request preparation, and repository design-system import as explicit post-MVP actions. + +## Explicitly out of scope for the current MVP + +- Silent background app creation or repository writes based only on a design prompt. +- GitHub App installation, repository upload, pull requests, deployment, domains, or hosted previews. +- Remote URL browsing inside Designer mode. +- Full Figma/vector/auto-layout parity, Webflow-style freeform authoring, component marketplaces, image mixing, variants, sketch-to-code, comments, queues, agent cursors, multiplayer, or sharing. +- Source instrumentation in an arbitrary dirty checkout. +- Automatic approval in Full mode or approval assembled from unrelated ordinary tool calls. +- Whole-repository commits/restores or hidden history rewrites. + +## Verification matrix + +### Shipped generated studio + +- Exact nested React Grab hit remains the selected element; no `closest([data-aiden-id])` promotion. +- Pointer, focused-element Enter, Escape, Preview, Select, Hand, Shift-across-artboards, and context-chip removal. +- Wrong frame source/capability/media/hash, oversized/unknown payloads, stale revisions, and non-Design previews fail closed. +- Network, parent DOM, Node, Electron, Aiden IPC, navigation, forms, downloads, and popups remain unavailable to the guest. +- React Flow pan/zoom/fit and desktop/tablet/phone frames work without iframe remount during ordinary movement. +- Empty, generating, ready, stale, unavailable, image, multi-artboard, revision, and export states at compact and wide widths. + +### Shipped source-backed path + +- Script detection, loopback URL and process ownership, readiness, stop, renderer invalidation, bounded logs, and direct-argv launch. +- Exact nested source mapping, ambiguous and unmapped failures, ownership, preimage hash, bounded range, Deny, Apply, stale postimage, and exact Undo. +- Keyboard and pointer paths, light/dark/high contrast/reduced motion, and 390/700/1000/1280px windows. +- Focused unit/integration/browser suites, `npm test`, `npm run type-check`, `npm run lint`, and `npm run build`. Signed package inspection and physical-process acceptance remain release gates, not open implementation work. + +## License and provenance + +- MagicPath and Open Design are behavior references, not sources of copied code or assets. +- React Flow and the vendored React Grab primitives are MIT licensed and recorded in `THIRD_PARTY_NOTICES.md`. +- Onlook is an Apache-2.0 reference for interaction and identity research. Before adapting implementation, record the exact source path/commit, preserve required notices, mark modifications, and verify every dependency independently. + +## Open decisions (not blockers for the shipped MVP) + +- Decide whether the first generated-to-source handoff creates an Aiden managed worktree by default or offers the current checkout first with a stronger warning. The safer default is a managed worktree. +- Decide whether a later exact-element style inspector should first support Tailwind literal edits or CSS custom-property edits. Both must use the same approval transaction. +- Decide whether nested-package discovery should be automatic with confirmation or begin with an explicit package/route picker. diff --git a/docs/plans/designer-mode-plan.md b/docs/plans/designer-mode-plan.md deleted file mode 100644 index f4671abc..00000000 --- a/docs/plans/designer-mode-plan.md +++ /dev/null @@ -1,378 +0,0 @@ -# Designer Mode Plan - -Status: revised implementation plan; Phase 0 is a go/no-go gate -Date: 2026-07-22 -Aiden baseline: `b6b0eff6bb55e0113a05cc8d069fce2c1be67b40` -Onlook baseline: `423e2e924366419e418ee049093872d535eea41a` - -Source basis: current Aiden and Onlook source, Aiden's project memory and product/UI references, three independent architecture reviews, `../../4-3-plan.md`, and `../../gemini3.6-plan.md`. - -## Verdict - -The product direction is good: **point at real UI → ask in plain language → approve a concrete change → hand off a clean diff** is a natural extension of Aiden. - -The earlier plan was not safe to implement as written. It assumed an unproven build-time identity path, treated an Electron `` as an ordinary panel, promised approval semantics that Aiden's current per-tool Ask hook cannot enforce, and copied Onlook's checkpoint strategy into real local repositories where it could absorb or overwrite unrelated work. - -This revision makes five changes: - -1. Prove preview containment and DOM↔source identity before building product UI. -2. Ship Vite + React + Tailwind only in the first supported slice; Next.js is post-MVP. -3. Use a main-owned, always-approved Designer Action transaction instead of ordinary agent writes. -4. Make exact, version-aware action undo the default; never stage, commit, or restore the whole worktree automatically. -5. Treat Onlook as an Apache-2.0 reference implementation, not a set of drop-in packages. - -Do not begin Phase 1 until Phase 0 has a written GO decision. - -## Outcome - -Aiden should let a designer select rendered UI in a local app, describe a change, inspect one bounded proposal, approve it, see HMR update the preview, and open an action-specific code review without losing the conversation. - -Aiden should not become Figma, Webflow, or a hosted Onlook clone. Preview is an opt-in work surface. Conversation, workspace identity, privacy, and approval remain primary. - -Positioning: Onlook is a hosted visual editor with direct code write-back; Aiden should be the local, permissioned path from visual intent to engineer-grade source control. - -## What the source actually says - -### Onlook - -| Layer | Verified behavior | Consequence for Aiden | -| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -| Preview | A project runs in a sandboxed browser iframe. A project-injected browser script and the editor communicate through Penpal. Both inspected Penpal endpoints allow `*` origins. | Reuse the interaction idea, not the bridge security model. | -| Source identity | `CodeFileSystem` mutates JSX/TSX source on write, adding random `data-oid` attributes, then builds `.onlook/index.json`. The indexer only maps AST nodes that already contain an OID. | A source-clean build transform and a separately run Onlook indexer cannot work together. Aiden needs one shared transform/manifest contract. | -| Runtime identity | Onlook distinguishes source OID, runtime DOM ID (`data-odid`), and component-instance ID (`data-oiid`). | Definition versus runtime instance is an MVP identity concern, not late toolbar polish. | -| Selection | A transparent gesture layer hit-tests the iframe, including open shadow roots, then renders editor-side overlays. | The coordinate system and invalidation contract must cover host bounds, scroll, scale, HMR, resize, and device scale. | -| Writes | Direct style actions are recorded in an in-memory history/transaction layer, written to source, and then reflected in the iframe. | The earlier “instant CSS first, async persistence second” description was inaccurate. Aiden should design its own proposal-first order. | -| AI context | Selected elements become highlight context and their content is refreshed. Normal design chats do not automatically attach the style guide on every turn; the style guide is attached during create/resume or read through a tool. | Main must re-resolve the full selection and source version before send. Automatic bounded style context would be an Aiden improvement. | -| Undo | Toolbar actions have in-memory undo/redo. AI completion also creates broad Git checkpoints with `git add .` and `git commit --allow-empty --no-verify`; restore uses a safety commit and `git restore --source … .`. | Do not copy the Git algorithm into a user's real checkout. | -| Local provider | `NodeFsProvider`, its watcher, terminal, tasks, and commands are placeholders. Live editor sessions use CodeSandbox. | Aiden should use its existing local services; there is no local Onlook provider to port. | -| License | The repository is Apache-2.0 and has no `NOTICE` file in this checkout. | Copied/adapted code needs a provenance ledger, the license, preserved notices, and modified-file marking where required. | - -Useful ideas to reimplement: deep element hit-testing, coordinate math, selection-context shape, parser fixture pairs, runtime DOM IDs, action grouping, and static token extraction. - -Do not copy wholesale: Penpal bridge code, MobX/editor-engine React surfaces, the internal UI color picker, the roughly 3,500-line `// @ts-nocheck` Tailwind translator, or the source-wide Babel/Prettier write path. - -### Aiden - -- The main renderer is context-isolated, sandboxed, and has Node integration disabled. Guest content is not currently enabled. -- The current Environment surface is a 480–720px Review/Files work surface. At the default 1000px window width it becomes an inert overlay, so merely adding a Preview tab would break the point-then-type loop. -- Terminal sessions are interactive shells. They do not own a dev command, readiness URL, logs, or server lifecycle. -- Ask approval is per mutating Pi tool call and applies only in Ask mode. Full mode bypasses it. The current tools cannot batch N files or enforce a selected source region. -- Review shows the whole working tree against `HEAD`; it has no action baseline or action-specific summary. -- Git and Files already have strong workspace-ID authorization, stale-snapshot checks, safe path resolution, temporary-index commits, and atomic version-checked saves. Designer Mode must reuse those safety properties. - -## MVP support contract - -| Capability | MVP support | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| Framework | Vite + React, JavaScript or TypeScript | -| Styling | Tailwind v3/v4 is the supported design-system path; static CSS context may be included, but no generic CSS visual editor | -| Preview-only fallback | Other loopback web apps may load if their frame policy permits it, but receive no source mapping or writes | -| Selection | One element at a time; maps to a source definition and clearly labels shared/repeated output | -| Multi-select / pinned context | Post-MVP, after stale-selection behavior is proven | -| Next.js / Turbopack / SWC | Post-MVP adapter with its own spike and fixture gate | -| Direct manipulation | Post-MVP; the first release is point → ask → approve | -| Remote URLs | Not supported in Designer Mode MVP | - -## Architecture decisions - -### 1. Preview containment: prefer an instrumented sandboxed iframe - -The first candidate is a sandboxed iframe plus a browser-only bridge injected by the Aiden Vite adapter. This keeps project code out of Electron's privileged preload world and follows the shape already proven by Onlook. - -Do not enable Electron `` for the MVP. It is disabled in Aiden today, and Electron [currently recommends alternatives](https://www.electronjs.org/docs/latest/api/webview-tag) because of webview stability and event-routing concerns. Phase 0 must still compare the iframe with a main-owned `WebContentsView` if frame headers or compositor behavior make the iframe unworkable. Any alternative must satisfy Electron's [security checklist](https://www.electronjs.org/docs/latest/tutorial/security). - -The iframe contract: - -- Canonical loopback `http:`/`https:` origins only; reject credentials, non-loopback hosts, unsafe schemes, redirects away from the approved origin, and the Aiden renderer origin. -- No popups, downloads, top navigation, camera, microphone, geolocation, clipboard, or filesystem privileges. -- Add the narrow `frame-src` needed by Aiden's CSP; do not widen `script-src` for preview code. -- Use a per-preview random capability in a typed `postMessage` handshake. Validate `event.source`, origin, capability, method, payload size, and schema on every message. -- Treat every guest field as untrusted. The guest may return opaque source/runtime IDs and geometry only; main resolves IDs to workspace files. -- The guest bridge exposes DOM inspection and temporary visual-preview operations only. It never receives `window.aidenAPI`, arbitrary IPC, Node, paths, file contents, or command execution. - -If an iframe requires changing `X-Frame-Options` or `frame-ancestors`, only the Aiden-owned local adapter may adjust the dev response. Never weaken a remote response. - -### 2. One adapter owns instrumentation and the source manifest - -The Vite adapter must emit both sides of identity from the same transform: - -- `SourceElementId`: opaque ID injected into rendered JSX during development only. -- `DomInstanceId`: unique runtime ID assigned to each actual DOM node so repeated `.map()` output can be hit-tested independently. -- `SourceManifestEntry`: workspace-relative path, component, tag, AST locator, start/end offsets and lines, dynamic/shared flags, source hash, and adapter version. -- `ManifestRevision`: changes atomically whenever transformed source changes. - -The adapter and main process may share a deterministic ID/manifest library, or the adapter may publish the manifest through an authenticated loopback side channel. They may not run independent random transforms. - -No derived index or code block belongs in the user's repository. Keep it in memory and under Electron `userData`, keyed by workspace identity, canonical root, adapter version, and source revision. Never persist absolute paths or code in renderer storage. - -The activation mechanism must be proven: Aiden should start the workspace's local Vite binary with a temporary wrapper/config outside the repository, preserving the user's config without editing it. If this cannot be made reliable, the Phase 0 decision is either: - -1. allow reversible source instrumentation only inside an explicit disposable managed worktree, with crash-safe strip/recovery; or -2. stop at read-only Preview. - -Never inject marker attributes into an arbitrary dirty checkout as an automatic fallback. - -### 3. Definition and instance are separate concepts - -A custom `