Account storage
Vizably runs no database of its own. A signed-in user’s entire account — profile, settings, saved scans — lives in storage they already own: one GitHub repository, or one Google Drive folder.
OAuth is used only to identify the user and obtain an API token. The user-owned store is the source of truth.
Two things fall out of that. The product costs almost nothing per user to operate, which is what makes it viable to offer for free. And the account is portable — it moves between devices, and it survives Vizably.
The connection flow
Section titled “The connection flow”Browse → select → validate → load or init.
- The user connects GitHub or Google via OAuth.
- They see storage they already have and pick one. GitHub repositories are
listed by the backend; Drive uses the client-side Google Picker, because the
drive.filescope cannot browse existing folders. - Vizably runs a fit-check against the selection.
- Vizably either loads the existing account or initialises the store.
On-disk layout
Section titled “On-disk layout”Rooted at the repository root, or the selected Drive folder:
<storage root>/├── vizably.json # manifest: identity, storage binding, cached summary└── scans/ ├── index.json # cache: lightweight list for the dashboard └── <scanId>_<host>.json # one immutable scan per fileTruth versus cache. The files in scans/ are the truth. index.json and
the manifest’s summary.scanCount are caches, always rebuildable by listing
scans/*.json. A reader reconciles on load and never trusts a cache over the
files.
The manifest
Section titled “The manifest”vizably.json carries a schemaVersion, a random account UUID, the storage
binding, settings, and a rebuildable summary.
Field rules that matter:
account.idis a random UUID minted at init and never changed. Provider login and email are display only.- Record stable provider ids, not names — repository node id, Drive folder id, owner id. Names change; ids do not.
schemaVersionis for breaking changes. A companionminReaderSchemaVersionlets a newer writer mark a store unreadable by too-old clients, so an old client reports “incompatible” instead of corrupting the store.- Caches never block a load. If
summarydisagrees withscans/, reconcile — do not fail. - Never store OAuth tokens, refresh tokens, API keys or any secret. Tokens are encrypted at rest in the session (AES-256-GCM) and never written to the user’s repository or folder.
The fit-check
Section titled “The fit-check”Given a selected storage, the backend returns a status, an optional reason, and probed capabilities.
| Condition | Status |
|---|---|
| No manifest, store otherwise empty | initializable |
| No manifest, store has unrelated files | unrelated |
| Manifest unparseable or missing required fields | invalid |
| Manifest found but not a Vizably store | unrelated |
schemaVersion newer than the server supports |
incompatible |
schemaVersion older but migratable |
loadable (migration required) |
Supported schemaVersion |
loadable |
Capabilities — canRead, canWrite, canCreate — are probed against the
provider and returned alongside. A read-only fork comes back loadable with
canWrite: false, so the UI can browse saved scans while disabling new saves.
Concurrency is the hard part
Section titled “Concurrency is the hard part”Multiple devices and collaborators touch the same store, so partial writes are expected rather than exceptional.
- Scan files are immutable. They are never rewritten in place.
- Init revalidates immediately before writing and creates the manifest only
if it still does not exist. A
validateresult from a moment ago may already be stale — another device may have initialised in between. - GitHub writes use the blob
shafor optimistic concurrency, and prefer a single commit carrying every changed file. On a stale-sha conflict, refetch and retry. - Drive has no multi-file transaction. Write the immutable scan file first, so truth is never lost, then update the caches using ETag or generation preconditions and let load-time reconciliation heal any gap.
Identity and disclosure
Section titled “Identity and disclosure”Identity is possession-based by default: whoever can read and write the store can load the account. The storage ACL is the account ACL. That is a deliberate trade and it needs saying out loud in the UI, not just in docs.
Two other things must be disclosed in the interface rather than buried:
- GitHub’s OAuth
reposcope is all-or-nothing — it cannot be narrowed to a single repository. - Deleting a scan removes the file and refreshes the caches, but GitHub history may still contain the deleted blob unless history is rewritten. Do not claim permanence you cannot deliver.
Implementer checklist
Section titled “Implementer checklist”- Manifest written with a random
account.idand stable provider ids - Scan files immutable, named
<scanId>_<host>.json, id is a UUID -
index.jsonandsummarytreated as caches, rebuilt on load - Fit-check returns status, reason and capabilities
-
initrevalidates and conditionally creates the manifest - GitHub writes use blob
sha; Drive writes use generation or ETag - No tokens or secrets ever written into the store
