Conversation
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 26549932 | Triggered | Generic High Entropy Secret | dd771c2 | lib/src/db/test.rs | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
This was referenced Apr 6, 2026
Per-write redb commits use `Durability::None` (no fsync) for throughput; redb only persists them once a *subsequent* Immediate commit (`flush()`) lands. The native server flushes on a periodic tick (serve.rs), but the browser worker never called flush at all — so every local write was rolled back to the last durable commit on the next page open. Online this was invisible (the server re-fetches and re-caches), but offline it was data loss: after disconnecting (or any reload while the WS is down), drives and resources read "Offline: resource not available locally" because the OPFS cache had silently reverted to empty. - wasm: expose `ClientDb.flush()` → `Db::flush()`. - worker: flush on a 1s tick whenever there have been writes since the last flush (mirrors the server; one fsync amortises a sync burst; idle tabs no-op). - e2e: regression test — a cached drive resolves from the ClientDb after a reload while offline. Claude-Session: https://claude.ai/code/session_01PfuLDVj966Lf5UGY497s2X
Guards the cold-load path: device 1 creates a folder, then a brand-new browser context (empty local DB, same agent) opens the drive and must see the folder — the drive sync populates the local index and/or the collection falls back to the server `/query`. Locks in the scenario the OPFS durability fix restored, so a future regression in cold-load/cross-device sync is caught. Claude-Session: https://claude.ai/code/session_01PfuLDVj966Lf5UGY497s2X
The Sync page "Local DB" toggle writes atomic-disable-client-db=1; on the next load App.tsx skips initClientDb, so every read goes to the server. That path was previously masked by the OPFS cache, so a server-only regression could hide until a user toggled Local DB off. Cover it for both a dev-drive and a regular UI-created drive: create a child, disable Local DB, reload, and assert the child still renders from the server's parent=<drive> query. Both pass today — a normally-created drive's children are indexed clean (no ?drive= hint mismatch on the DID), so the server-only cold load surfaces them. Locks that in against future query-path or DID-indexing regressions. Claude-Session: https://claude.ai/code/session_01PfuLDVj966Lf5UGY497s2X
The chat input was `disabled={!isProviderAvailable(...)}`, which made the
whole editor read-only whenever the active model's provider was unreachable
— e.g. Ollama down at localhost:11434, or no OpenRouter key. Worse, this
only surfaced after `atomic.ai.setupComplete` was already true (the
AISetupPanel overlay had stopped showing), so the user got a silently
non-typeable input with no explanation and no way forward.
Now the input is always editable: you can compose a message even while a
provider is unreachable or still being set up. Only the SEND button is gated
on an available provider, and it re-enables automatically when the provider
comes back (the Ollama reachability check polls). A notice above the input
states the actual reason (Ollama unreachable / no OpenRouter key) with a
"Set up a model" button that re-opens the provider setup.
Claude-Session: https://claude.ai/code/session_01PfuLDVj966Lf5UGY497s2X
Follow-ups to the always-typeable input change:
- Ollama "Checking server…" no longer sticks forever. `useIsOllamaUrlValid`
returned a bare boolean, so callers derived `checking` as `urlSet && !valid`
— which stays true once a check FAILS, not just while it's in flight. The
hook now returns `{ valid, checking }` with a real settled/in-flight
distinction, so a failed Ollama check shows "Not responding".
- The provider-unavailable notice moves ABOVE the input box (it was rendering
inside the bordered input, which looked odd), and its action is now a proper
Button instead of a bare styled span.
- The model selector shows the provider in the subtitle ("OpenRouter • …",
"Ollama (local) • …") and includes it in each option's search text, so
typing "ollama" or "openrouter" surfaces all of that provider's models.
Claude-Session: https://claude.ai/code/session_01PfuLDVj966Lf5UGY497s2X
…WASM
Root cause: a server snapshot (a resource GET/SUB, or a `/query` collection
page) can arrive over the WS BEFORE the lazily-loaded Loro WASM has finished
initializing. `importLoroUpdate` then buffers the bytes instead of
materializing them — and nothing guaranteed the buffer was ever applied, so
properties read as undefined. Two visible symptoms, one cause:
- A Drive/Folder rendered as a bare `did:ad:…` subject with no class, no
title, `loading=false` AND `error=null` (a silently-broken resource), or
stuck on a spinner sitting on top of already-materialized data.
- The drive's children sidebar landed empty ~half the time:
`fetchPageFromServer` read `totalMembers`/`members` off the still-buffered
`/query` resource, got undefined, threw `total-members is not a number`,
and the throw was swallowed by the caller's `.catch` — no query, no error,
an empty collection.
Fixes:
- resource.ts: when a snapshot is buffered (Loro not yet loaded), register a
`LoroLoader.onReady` callback that materializes the doc and re-notifies
THROUGH THE STORE (useResource/useValue only re-render on store.notify, not
the Resource's own eventManager). The `loading` getter reports `true` while
a renderable-less buffer is pending; `getLoroDoc` clears it once the
materialized cache has a class.
- collection.ts: `await enableLoro()` + force-materialize the `/query`
resource before reading `members`/`totalMembers`, so a snapshot that beat
Loro no longer yields an empty page.
- store.ts: OPFS cold-load guard treats a server-managed-skeleton-only hit
(parent/drive/createdAt/lastCommit, no class) as a miss; offline path waits
briefly for the WS instead of instantly failing a reload.
- websockets.ts: a SNAPSHOT-flagged UPDATE replaces seeded partial Loro state
so the authoritative snapshot wins over an earlier partial SUB push.
- SideBarDrive.tsx: gate the children list on the reactive `useChildren`
state, not `driveResource.isReady()` (a proxy read the React Compiler
memoizes on the stable ref, so the list stayed empty after ready flipped).
- useChildren.ts: one child failing to load no longer rejects the whole
Promise.all and blanks the sidebar.
- vite.config.ts: exclude `@tomic/lib`/`@tomic/react` from optimizeDeps so a
watch-rebuilt `dist` is served directly (the pre-bundle cache is keyed on
the lockfile hash, not dist content, so changes were invisible until
`--force`).
Tests: loro-load-race, opfs-incomplete-snapshot, snapshot-replace (unit);
did-folder-reload, local-db-off-did-render (e2e).
Claude-Session: https://claude.ai/code/session_01PfuLDVj966Lf5UGY497s2X
Configure fastembed and ONNX Runtime (ort) with explicit feature sets (rustls-tls, download-binaries, …) instead of crate defaults. Adjust the Dagger release pipeline accordingly: install protobuf-compiler, and build the aarch64-unknown-linux-musl target with the portable `light` feature set since the fastembed/ort vector-search stack ships no ONNX Runtime binaries for aarch64 musl. Claude-Session: https://claude.ai/code/session_01PfuLDVj966Lf5UGY497s2X
…tch-up
CodeBlock's copy button called `navigator.clipboard.writeText` directly, which
browsers reject on insecure origins (local HTTP hosts) — the copy failed
silently. Fall back to a hidden-textarea + `execCommand('copy')`, and if that
also fails, select the code block and show an actionable error toast.
Regenerate the wuchale locale catalogs (de/en/es/fr) to pick up the new string
plus the AI chat strings added earlier this session.
Claude-Session: https://claude.ai/code/session_01PfuLDVj966Lf5UGY497s2X
vite.config.ts: expand the react-compiler comment with why we stay on the Babel plugin (oxc-transform's `reactCompiler` is a no-op; the archived community oxc port emits duplicate helpers that break multi-component files). planning: link the nextgraph-interop proposal. Claude-Session: https://claude.ai/code/session_01PfuLDVj966Lf5UGY497s2X
Three committed files failed the data-browser `format-check` (pre-existing drift, unrelated to any feature change). Reformat them so `pnpm lint` passes cleanly. Whitespace/style only — no logic changes. Claude-Session: https://claude.ai/code/session_01PfuLDVj966Lf5UGY497s2X
`docs/atomic.css` was an empty file removed in 547f6a8 as "unreferenced" debris — but `book.toml`'s `additional-css` DID reference it, so once that deletion landed on this branch the mdbook html backend fails with "failed to open `atomic.css` for hashing" and the CI docs build exits 101. The file was empty (no styling), so completing the cleanup by dropping the reference is the correct fix; `mdbook build` passes again. Claude-Session: https://claude.ai/code/session_01PfuLDVj966Lf5UGY497s2X
`migrate_from_sled` silently migrated ZERO user resources from any pre-v3 sled store, then renamed the source dir to `.bak` and reported success. Validated against a real v2 backup: 231 of 62,212 resources survived (only bootstrap vocabulary). After these fixes all 61,804 user resources migrate intact. Two independent bugs, each hidden behind the last (proof this path was never run against real v2 data): 1. migrate_from_sled read Tree::Resources (`resources_v3`) directly WITHOUT first running `migrate_maybe`, so a v2 store's data — which lives in `resources_v2` — was never seen. Run the schema migrations (v0→v1→v2→v3) in place before reading. 2. `ValueV2` deserialized the JSON datatype as variant `Json`, but v2 data on disk was written when it was named `JSON` (the DID refactor #1139 renamed it). rmp_serde tags enum variants by name, so every resource holding a JSON value failed with `unknown variant 'JSON'`. Restore the historical wire name with `#[serde(rename = "JSON")]`. Also tolerate the removed `YDoc` variant (the unreleased Yjs experiment) so a dev store holding those values doesn't abort the migration. Migration code is db-sled-gated; the default db-redb build is unaffected. Claude-Session: https://claude.ai/code/session_01PfuLDVj966Lf5UGY497s2X
Two plumbing gaps meant the (now-correct) migration never fired for a real user upgrading from a pre-redb server: 1. The migration is `#[cfg(all(db-redb, db-sled))]`, but the server only enabled `db-redb` — so `migrate_from_sled` was compiled out of every release. Add `db-sled` to the server's atomic_lib features. It's inert unless a legacy sled store is present, so fresh installs pay nothing. 2. The auto-migration looked for `<store>/sled/`, but pre-redb servers stored the sled DB directly at the store root (`store/db`, `store/conf`). Detect that legacy layout (db + conf at root, no `sled/` subdir) and relocate the sled files into `sled/` before migrating — otherwise the trigger never fires, and `migrate_from_sled`'s rename-to-`.bak` would rename the whole store dir and clobber the freshly-written redb. Validated end-to-end with the DEFAULT binary against a real legacy-layout v2 backup: 62,212 resources migrate, leaving `atomic.redb` + a preserved `sled.bak/`. Claude-Session: https://claude.ai/code/session_01PfuLDVj966Lf5UGY497s2X
The /app/invite route built the invite subject and handed it to ResourcePage, which picks the view component from the resource's `isA`. When the invite resource loaded without its class materialising — a server snapshot arriving before Loro WASM is ready, especially in an insecure context (plain HTTP on a non-localhost origin, where the WASM is unstable) — `selectComponent` fell back to `ResourcePageDefault`. The user then saw the raw, class-less resource (an "agent"-looking blob with a `destination` but no Accept button and no redirect) instead of the invite welcome screen. The route already KNOWS the subject is an invite, so it shouldn't depend on class detection at all. Render InvitePage directly. The accept flow only needs the token (which is in the URL), not the resource's class, so this is robust against the materialisation race. Verified live: InvitePage renders with a working Accept button even when the resource is only partially loaded. Claude-Session: https://claude.ai/code/session_01PfuLDVj966Lf5UGY497s2X
… + post-Jun22 transcript edits
Reconstruction gaps from did/did-rebased2 divergence found via compile checks: - lib/src/sync/mod.rs: add 'pub mod policy;' (policy.rs existed, decl didn't) - browser/lib/src/index.ts: re-export schema.js + schema-lock.js
…ent + old scratch files) These ~39 files were not in your pushed 'did' branch and predated your June-22 work. They were stale did-rebased2 content my transcript replay wrongly merged in, and were the sole source of all test/typecheck failures. Recovery now = did + your June 22-26 work.
…+ truncated fns)
Reconstructed from call-site contracts + atomic-saas backend (all inference,
marked [RECOVERY-RECONSTRUCTED]):
- helpers/cloud/{api,binding,session,enrollmentApi,index}.ts (never captured)
- completed helpers/cloud/recovery.ts:getRecoverySecret
- completed helpers/saasRecovery.ts:getSaasApiBase + createSaasEnrollment
- restored SettingsAgent.tsx sign-in state/handler + component imports
data-browser typecheck now clean.
- GettingStartedFlow: when arriving from the cloud portal (?from_saas=true), skip the generic Create/Sign-in choice, prefill the username from the verified account email (getCloudAccount), and auto-enroll the new drive in cloud sync (createCloudSyncEnrollment) after creation. - NewIdentitySection: new defaultProfileName prop to prefill the profile field. - helpers/cloud/api.ts: point getCloudApiBase at the real control-plane backend (:3030/api), not the portal (:49237). Verified against the running atomic-saas.
… normal view A fork copied the original's `read` / `write` grants, so forking a *published* page produced a draft carrying that page's `read: [PUBLIC_AGENT]` — a draft that was public the moment it was created, which is the one thing a draft must never be. A fork now inherits its privacy from the folder it lands in, and a merge no longer pushes the fork's ACL back onto the original: publishing is a move, not a copied grant. A draft carries its content class, so it already renders through that class's own view. Send `Edit as draft` to the normal view rather than the edit form, and add a DraftBar above it — otherwise nothing on screen says you are not looking at the original. The bar links home and offers Merge / Discard. Forking a Drive is no longer offered: a Drive is a container, not content. Adds the SDK sugar this wants: `resource.fork(parent)`, `draft.mergeIntoOriginal()` and `resource.isDraft`. Named `mergeIntoOriginal` because `Resource.merge` already means something else — folding another Resource's propvals into this one.
Drop the rounded border so it spans the view it sits above rather than floating in it.
… original A plain squash merge wrote every draft property onto the original, so a property the draft never touched still carried its fork-time value — merging silently reverted any edit the original received since the fork. Two people using the flow (one edits, the other's draft merges later) lose data with no warning. A fork now records `forkBase`: the original's content properties at fork time, as a self-contained JSON snapshot. Merging writes only the properties the draft actually changed relative to that base, so an untouched property is left alone and a concurrent edit survives. Where both sides changed the same property it is a conflict — surfaced via `diffDraft`/`DraftChange.conflict`, counted in the DraftBar, and `onConflict: 'throw'` aborts — rather than silently resolved. Snapshot rather than replaying the forked-from commit: commit retention is optional node policy, so a snapshot works on a pruned or offline node. Tests add the two-agent concurrent-edit scenario that reproduced the bug, plus conflict detection and the "untouched property is not a change" invariant. The DraftBar shows the change/conflict counts and confirms before overwriting a conflicting property. `resource.fork()` / `resource.mergeIntoOriginal(opts)` / `diffDraft` are the SDK surface. Design + remaining flow gaps (discovery, diff UI, suggest-for-non-writers, reject-with-reason): planning/drafts-and-suggestions.md
Adds an end-to-end spec for the draft flow (fork → edit → merge, and discard) against a real server — the first e2e coverage for drafts, and it exercises the `drafts` ontology being seeded on the server and in the WASM client store. Writing it surfaced a real bug the unit tests couldn't: the DraftBar computed `diffDraft(resource, original)` directly in the render body, so the React Compiler memoized the result on the two Resource proxy references. Those refs don't change when a resource mutates internally, so the change/conflict counts froze at first render — the bar always said "No changes yet" and the Merge button stayed disabled no matter what you edited. Subscribe to each resource's latest commit and key a useMemo on it so the diff recomputes when either side commits. The spec uses a Folder (title is a plain `name` propval). A DocumentV2 keeps its title/body in a Loro `doc` container that the propval diff can't see — forking one loses the body and the propval merge can't carry it. That doc-container limitation is already recorded in planning/drafts-and-suggestions.md as needing an oplog merge.
…m (no inbox) A PendingDrafts bar on a resource's normal view runs a reverse query (originalSubject == thisResource, drive-scoped) and lists the drafts proposing changes to it, each a link to review. This is how a reviewer discovers a draft without any inbox or push: a same-drive draft rides ordinary drive sync into the reviewer's own replica, where a local query finds it. It only shows drafts the reviewer can already read — a proposal on someone else's drive is invisible here by design, and needs the cross-agent delivery primitive (phase 2). Also fixes `useCollection` to pass `drive` through to the CollectionBuilder — the type allowed it but it was dropped, so indexed queries couldn't be drive-scoped. e2e covers the reviewer opening the original and seeing the pending draft.
A DocumentV2's text lives in a Loro `doc` container that the propval-based fork/diff cannot see, so drafting a document previously copied no body and offered no way to merge one. Fork now seeds the draft's Loro doc from the original and records `forkVersion` (the version vector at fork time); merge exports only the draft's ops since that version and imports the delta into the original — a true op-level merge, so a concurrent body edit on each side both survive. The body merge is kept independent of the three-way propval merge by snapshotting and restoring the original's propvals around the delta import. The draft bar offers Merge on the strength of a body edit alone (`hasBody`, no changed property). `Edit as draft` is gated off Canvas, whose content lives in stroke lists rather than the `doc` container the fork seeds, so forking one would silently lose its body. Covered by a new `drafts.test.ts` CRDT case and an `edit a document body as a draft` e2e; existing drafts unit + e2e suites stay green. Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
…genda-minutes) Brings the meeting agent's work into the did branch: one Meeting-only resource carrying rich-text Agenda/Notes/Minutes plus child chat messages, a dedicated Meeting page, an explicit prepare-then-start lifecycle, a follow-along panel, and a clean cutover from the temporary Meeting+ChatRoom shape (no backwards compatibility). Adds the `meeting.json` default ontology and registers it in populate.rs; updates chatroom.json and the dataBrowser ontology accordingly. Integration notes: - The `meeting` action in the actions registry takes the meeting agent's newer `startMeeting()` -> `openMeetingPanel(meeting)` flow (over the did branch's earlier no-arg variant), matching their ActionContext wiring. - Locale catalogs regenerated with a single-writer wuchale extraction so the union of draft and meeting strings is consistent (no merge-marker corruption). - planning/README.md keeps both the meetings (Built) and drafts (Active) rows. Verified: data-browser typecheck clean; drafts (10) + meetingLifecycle (3) unit tests green; drafts (4) + meetings (3) e2e green against the reseeded server. Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
…blished content)
"Draft" was overloaded: the class *required* an originalSubject, so the only
thing you could make was a proposed change to something that already exists —
there was no way to draft brand-new content, and the word misdescribed the
feature. Split the two orthogonal capabilities the design already identified:
- Fork (renamed from Draft): a proposed change to an EXISTING resource. Carries
the content's classes alongside the Fork class + originalSubject, and merges
onto the original (three-way propvals + CRDT body). Class `Draft` -> `Fork`,
ontology `drafts` -> `forks`, `isDraft` -> `isFork`, `diffDraft`/`mergeDraft`
-> `diffFork`/`mergeFork`, DraftBar/PendingDrafts -> ForkBar/PendingForks,
actions editAsDraft/mergeDraft -> editAsFork/mergeFork, forks live in a per-
drive Forks folder. Behaviour is unchanged; only names and copy move.
- Draft is now new, unpublished content — defined by LOCATION, not a class: any
resource in the drive's private Drafts folder. A new `New draft` action creates
content there; publishing is just moving it somewhere publicly readable
("visibility is location"), no class to add or strip. Answers "how do I make a
new draft that isn't based on an existing resource".
Discovery: forks proposing a change to a resource still surface as the
PendingForks bar on that resource; the Drafts/Forks folders appear in the drive
tree.
Ontology subjects change (classes/Draft -> classes/Fork, ontology/drafts ->
ontology/forks), so existing stores need --repopulate-defaults; pre-release, no
data migration.
Verified: lib fork tests (10) green; forks e2e (4) + new-draft e2e (1) green
against the reseeded server; typecheck + lint + format clean on the changed
files.
Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
…gy split Renames the fork mechanism from "Draft" to "Fork" throughout the design record, and documents Draft as its now-separate meaning: unpublished new content defined by location (a resource in a private Drafts folder), published by moving. Updates the code identifiers referenced by the doc (diffFork/mergeFork/forks.ts/ForkBar/ PendingForks) to match the renamed symbols. Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
…ackage
Five specs had gone stale against @tomic/lib API changes, failing the e2e
package's typecheck (all still passed at runtime — the offending code was inside
page.evaluate, which runs as plain JS):
- clientdb-edit-persistence: `set`/`save` dropped their trailing `store` arg;
`fetchResourceFromClientDb` is now private — reach it through a typed cast.
- multi-property-filter: assert the drive subject non-null at the evaluate arg
sites (it is always set by the test).
- offline-create-then-online: `createDrive`'s 2nd arg is now `CreateDriveOpts`,
so pass `{ description }` rather than a bare string.
- opfs-init-perf: drop a now-unused `@ts-expect-error` (OPFS `entries()` is typed).
- sync: the outbox is Loro-delta based now (no `commits` list) — surface the
genesis/baseVersion fields in the failure diagnostic; pass the waitForFunction
arg as an object so the typed overload resolves.
Verified: e2e `tsc --noEmit` clean; all 9 tests across these specs pass.
Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
The data-browser seeds a fixed list of default ontologies into the WASM client store at startup (`bootstrap.ts`) so property definitions resolve locally without a network fetch. `meeting.json` and `forks.json` were added to `lib/defaults` (and seeded server-side) but never added to this client list, so the client had no local definition of e.g. `currentMeetings`. The symptom: ending a meeting took ~10s. `endMeeting` sets `currentMeetings` on the drive with validation on, which calls `store.getProperty(currentMeetings)`; missing locally, that fell through to fetching the `https://atomicdata.dev/...` subject, which times out (~10s) before validation is skipped. Same latent cost for any validated write of a meeting/fork property (meeting start on a cold client, fork edits). Bootstrapping both ontologies makes those lookups resolve locally. Measured: meeting end 10.4s -> 0.3s, with no change to the meeting code itself. CI already copies the whole `lib/defaults` dir into `lib-defaults`, so the new imports need no pipeline change. Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
Joining and leaving a meeting were pure local state (follow/unfollow), so the
meeting chat only ever showed "Started"/"ended" — you couldn't tell from the
minutes who actually attended. Now a joiner's client posts a follow-event
message to the meeting: "Joined the meeting." when they join, "Left the
meeting." when they explicitly leave.
- The signal is `followedSession` (the meeting of the leader a joiner follows);
the leader is excluded (their arrival is the Start marker).
- A departure is recorded only while the meeting is still live —
`postMeetingLeave` skips it once `meetingEndedAt` is set, so a leader's End
doesn't make every follower's auto-unfollow spam "Left" on top of
"The meeting has ended.".
- sessionStorage (per meeting, cleared on tab close) dedupes across reloads so
a refresh mid-meeting doesn't re-announce, mirroring the leader's
`meetingStorageKey`.
Covered by a new two-context e2e ("records join and leave in the meeting
chat"). Also reflows forks.spec.ts, which a path-quirk had skipped in the
earlier format check.
Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
…r focus Lands the remaining meeting UI work: the follow-session panel header shows a button-styled "Notes" link (was the plain "Open notes" link) and a subtle Leave/End button; the Meeting page focuses the editor after committing the title; the drive-structure helper and resource sidebar account for the Meeting class. Updates the follow-along e2e to click "Notes" so it matches the renamed control. Verified: data-browser typecheck clean; all four meetings.spec tests pass (including the follow-along and the join/leave recorder). Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
`fetchPersonalDriveSubject` fetched the Agent from the server and then read it back with `getResourceLoading`, which can return the resource still in its loading state — so `personalDrive` / `drives` could read empty and the home drive would wrongly fall back to `initialDrive` (breaking Favorites on a dev drive). Await `store.getResource` instead: it returns the resolved resource from the local-first store, fetching only when needed. Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
…havior Brings the public docs in line with what the implementation now does: - did.md: genesis is a Self-Verifying Genesis Certificate (v2) — identity is derived from the inline binary cert, verifiable offline without a commit fetch. - authentication.md: WebSocket auth uses the v2 binary-first protocol (AUTH frame tag 0x01 carrying the serialized credentials). - websockets.md: both subscription pushes and direct GET responses now carry the lastCommit identifier as commit_id; drops a stale planning-doc reference. - roadmap.md / commits/concepts.md: prose refresh. Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
New planning note proposing Dashboards: composable views over data built on the Table View filter pattern and the create_table LLM-authoring precedent. Adds it to the planning index. Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
…network Covers the offline signup path: with fetch disabled and a mock ClientDb, saving a new Agent resource returns 'offline' and persists jsonAd + a Loro snapshot; after clearing the in-memory store, the agent resolves back from local storage (not a server fetch), with its name intact. Cleaned up debug logging and an `any` cast from the original draft. Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
First slice of planning/virtual-drive.md, hosted in the Tauri desktop app (not the headless server): a local NFS v3 server, bound to 127.0.0.1, that the OS mounts so your Atomic drives appear as a filesystem. The mount root lists every Drive the node can see; Folders are directories; Files stream their blob bytes. - `desktop/src/vfs.rs`: `AtomicNfsFs` implementing `nfsserve::NFSFileSystem` against the embedded `atomic_lib` store — `readdir`/`lookup`/`getattr` over the `parent`-query hierarchy, `read` slicing blob bytes from `Tree::Blobs` lazily (never on readdir/getattr), an in-memory fileid<->subject map, and filename sanitization at the materialization boundary. - Started from the embedded server's `on_ready` hook in `lib.rs`, desktop-gated off Android/iOS (neither can mount NFS — they use provider APIs). Logs the exact `mount` command; auto-mounting is a follow-up. Read-only for now (`capabilities()` is ReadOnly; mutating methods return ROFS), but built read-write-ready per the request: the id map allocates ids, names are already sanitized, and the fs carries a commit `source_id` for echo suppression — so the write path is filling in stubs, not a reshape. Covered by unit tests (store hierarchy → dirs/files, full + partial blob reads, name sanitization). Desktop crate builds; nfsserve/tokio/hex/async-trait added as desktop-only deps. Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
Gives the app ownership of the mount lifecycle instead of auto-starting the NFS server at boot. The desktop backend exposes three Tauri commands — `virtual_drive_start` / `virtual_drive_stop` / `virtual_drive_status` — backed by a `VfsController` that runs the NFS server on its own thread and stops it by dropping a shutdown channel. A new "Virtual drive" section in App Settings (shown only in the desktop Tauri app) toggles it and shows the exact `mount` command to copy. - `vfs.rs`: `VfsController` (start/stop/status) + `mount_command()`; `serve` now selects on a shutdown receiver. Auto-start removed from the boot hook. - `lib.rs`: manage the controller and register the commands, desktop-gated. - Frontend: `helpers/virtualDrive.ts` (invoke wrappers, availability gate) and `Settings/VirtualDriveSettings.tsx`. Rust vfs tests still green; data-browser typecheck + lint clean. Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
The copy-paste `mount` command was bad UX. On macOS a normal user can mount NFS
to a folder they own without sudo (verified: an unprivileged mount attempt fails
with "connection refused", not a permission error), so the app does it directly.
- `VfsController::start` now starts the NFS server, waits for it to listen, and
runs `mount` to `~/AtomicDrive`; `stop` unmounts and stops the server. Added
`open_mount()` (Finder/Explorer) and a `virtual_drive_open` command. Status is
now { running, mounted, mount_path } — the mount command string is gone.
- Settings UI: a single Turn on / Turn off toggle, the mounted path, and an
"Open" button. No terminal, no command to copy.
Linux/Windows: `mount` there usually needs privilege (Linux) or a different
invocation (Windows); the error surfaces in the UI for now — auto-mount on those
platforms (or native File Provider APIs) is a follow-up.
Rust vfs tests green; data-browser typecheck + lint clean.
Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
Makes the NFS mount read-write. `create`/`write`/`mkdir`/`remove`/`rename`/ `setattr(truncate)` map onto signed commits, so editing a file in Finder writes it back as an Atomic commit that syncs with the user's rights. - Writes stage bytes in RAM keyed by fileid; a background flusher commits a file once its writes have been quiet for 500ms (commit coalescing — an editor's write burst becomes one commit, not one per syscall). read/getattr consult the staging buffer so write-then-read is consistent before the flush lands. - Committing hashes the bytes (BLAKE3) → blob, then updates the File resource's internalId/blob/filesize/downloadURL and saves. Files are created with the full valid shape a server upload produces (File requires downloadURL). - Commits are signed by the store's default agent, which the desktop app points at the signed-in user via `adopt_agent` — no separate server agent, and the writes carry the user's identity so peers accept them. - Admission caps (max file size, name length) + filename sanitization applied on the write boundary. Dropped the `ro` mount flag. Deferred (per planning/virtual-drive.md): content-defined chunking (write amplification), disk-backed fileid map, readdir cursor, conflicted-copy naming. Covered by an integration test: create → write → staged read → flush → persisted commit, plus mkdir, rename, truncate, remove. All 4 vfs tests green. Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
…s remounts) NFS clients and the OS cache a file's numeric id and assume it never changes, but the map was in-memory and rebuilt each mount, so a file could get a different id after turning the drive off and on. Persist the `fileid <-> subject` map in the store's `PluginMeta` tree (namespaced keys + a counter), with the in-memory HashMaps kept as a cache in front so getattr/read don't hit the kv store on every call. `remove` reclaims the entry so the map doesn't accumulate dead ids. Covered by a new test: the same subject keeps its fileid across a fresh `AtomicNfsFs` with an empty cache. All 5 vfs tests green. Deferred #2 of 4 done (planning/virtual-drive.md). Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
`readdir` re-fetched and re-sorted a folder's entire child set on every page, so listing a large folder was O(N²). Build the listing once per enumeration (a fresh `ls` starts at cookie 0), cache it per directory, and page from the cache; create/mkdir/remove/rename invalidate the affected directory so changes show immediately, and a short TTL bounds staleness within a single enumeration. This removes the per-page re-scan (O(N) per enumeration). The store-level cursor the doc describes (O(log N) per page, also useful for browser pagination) stays deferred — it needs a query-engine change, not a VFS-only one. New test pages a 25-entry directory in chunks of 10 and gets every entry once. All 6 vfs tests green. Deferred #3 of 4 addressed at the VFS layer. Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
…unchanged Content-addressed no-op skip in the flush path: if the staged bytes hash to the File's current internalId, there's nothing to commit, so skip the blob write and the commit. Avoids redundant churn when an editor saves a file it didn't change. This is a partial mitigation for write amplification; the core large-file case (a 1-byte edit rewriting a whole-file blob) still needs content-defined chunking, which is a File-model change, not a VFS-local one. All 6 vfs tests green. Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
Large files (> chunk-max) are now split with FastCDC and stored as an
ordered list of `did:ad:blob:` chunk refs on a new `chunks` File property.
Small edits re-hash only the changed chunks; unchanged chunks dedupe by
hash. Small files keep the single whole-file blob path.
The server's download handler reconstructs chunked files by concatenating
their chunk blobs, both for the File-resource path and the
content-addressed `/download/files/{hash}` path (which finds the chunked
File by its whole-file internalId, since no whole-file blob is stored).
Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
Opening the mount root on a real store (2578 drives, 2.1 GB db) took ~762s and the mount appeared to hang. Four stacked costs, each O(store) or O(entries²) on the hot readdir/lookup/getattr path, fixed: - Directory-listing queries (`drives`, `children`) ran nested, invoking each resource's class extenders + a durable redb flush per entry. Make them non-nested — a listing only needs subjects, names come from a cheap per-entry read. - Minting a stable fileid wrote 3 keys as 3 separate redb commits per entry (~hundreds per listing, contending with the live writer). Mint in memory and persist all new ids in one batched transaction off the hot path; `forget` drops any not-yet-persisted entry. - Reading each entry's name went through the full `get_resource`, which re-decodes and decompresses the resource's entire Loro snapshot. Add `Db::get_resource_shallow` (materialized propvals only) and use it for all listing reads and `getattr`. - `lookup` re-enumerated the whole directory on every call; the OS issues one per entry after a readdir, making a folder open O(entries²). Resolve from the cached listing instead (shared `ensure_dir_cached` helper). Root listing of 2578 drives: ~762s -> ~0.01s cold, instant warm. Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
…the user Refs #1154 (two-way filesystem binding). Building on the file/folder mount: - Mirror the whole hierarchy: Folders are directories, Files are files, and every other resource (documents, tables, chatrooms, …) becomes a read-only macOS `.inetloc` link. Opening one hands off to Atomic desktop via an `atomic://open?subject=…` deep link, which the frontend now routes to `navigate(constructOpenURL(subject))` instead of the pairing flow. - Scope the mount root to the signed-in agent's own drives (personalDrive + the agent's `drives` list) rather than every Drive in the store. A desktop node syncs drives from other peers, so Sudo listed thousands the user has no rights to — and opening one bounced the auth-gated UI to the welcome page. Signed out, the root is empty. - Self-heal the OS mount: force-unmount any stale mount (left by a hard-killed run) before mounting, so toggling the drive on can't fail with EPERM. Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
`read` rebuilt the whole file from its blobs on every NFS read window, so a sequential read-through of an N-byte file was O(N²) (and Loro-decoded the resource per window). Add a single-entry read cache keyed by fileid + the file's whole-file hash: a read-through reconstructs once, every window slices the cache, and an edit (new hash) self-invalidates. `read`/`getattr` now use the shallow (propvals-only) resource read, dropping the per-window Loro decode. Sequential-read bench: 137 -> 387 MiB/s at 32 MiB, and it no longer degrades with file size. Adds opt-in `#[ignore]` benches (`mod benches`) for sequential read, save latency, per-edit DB growth (documents the missing blob GC), and listing latency — run with `--ignored --nocapture`. Planning doc updated with implementation status, the numbers, and the remaining perf gaps (blob GC, write amplification, true range reads). Claude-Session: https://claude.ai/code/session_01VddwtxveM8Zqsf765aQSHY
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Most insane PR ever.
did:adResolvingdid:adlocal-first resources #1146 , refactors subject completely Refactor Subject (resource identifiers / urls / dids / ids) #1139Checklist