FileLoveNote / build packageKit revision · 28 October 2026

10 / 15 · Data Structure

Records, fields and relationships

Use this data dictionary when building the stores, API payloads and sync flow. It shows what the main records look like and where they belong.

Logical tables, backed by JSON, IndexedDB and files.

The existing app has no SQL database. A table below describes a collection or record shape, not a request to add a relational server database. Field names are technical identifiers; all examples are fictional. This chapter documents the implementation checked on 26 October 2026, with reconstruction requirements and compatibility limits marked explicitly.

On narrow screens, scroll each table sideways to read every column. Tables can also receive keyboard focus.

1 · Storage map

Logical collectionKey / locationContent and ownership
Usersusers.json → users[userKey]Server account registry; identity and account status.
Sessionssessions.json → [sessionId]Server login sessions; signed session ID in an HttpOnly cookie.
NotesIndexedDB; optional Drive .mdReadable titles, writing and structured editor blocks belong to the browser and the user’s Drive.
FoldersLocal graph / folder:<id>; data.json → noteFolders or foldersSeparate Notes and Files trees; folder names and hierarchy can be server metadata.
Pending notes, bases and tombstonesAccount Drive IndexedDB recordsDurable outgoing work, last confirmed document and deleted markers.
Drive note indexdata.json → driveNotes.files[noteId]Technical metadata only after legacy migration; never a durable note projection.
Media metadatadata.json → files[name] or googleDriveFiles[fileId]Playback, annotations and source information. Original Drive file bytes stay in Drive.
Drive connectiondata.json → googleDriveConnectionServer-side encrypted refresh token and connection metadata.
Canvasesdocument-assets/<docId>/canvas-<canvasId>.jsonSeparate server-side canvas document; may contain user text and references.
Sync ticketsencrypted-sync/<ticket>.jsonTemporary encrypted packet and result; remove when result is consumed.

Each server data location is relative to the authenticated account’s storage root, normally APP_DATA_DIR/users/<userKey>/. The legacy owner can use APP_DATA_DIR/ itself. The global users and sessions files live at that top level. Never choose an account root from an untrusted request field.

2 · Relationships and identifiers

RelationshipCardinality / referenceRule
User → sessions and account storesOne user to many sessions and recordsOwnership comes from authenticated userKey, not from a note ID alone.
Folder → child foldersOne to many through parentIdOnly the collection root has no parent; reject cycles and invalid parents.
Notes folder → notesOne to many through DocumentRecord.folderIdNotes root is folder-notes; Files root is folder-home. Do not mix the trees.
Note ↔ Drive fileOptional technical mapping noteId → fileIdA logical note ID is different from Google’s file ID and revision string.
Note → base / pending / deletedZero or one current record per note and key prefixA pending document: null means delete. A token identifies a specific queued version.
Note ↔ media sourcesMany to many through editor blocks and source referencesStore references/URLs, not duplicate GB-sized file bytes. These are embedded references, not a SQL join table.
Note → canvases → elements / connectorsOne to many; Canvas.docId and endpoint IDsConnectors reference element IDs within the same canvas. A noteRef.docId points to another note.
User (userKey)
  ├─ Session [sessionId]
  └─ Account storage
       ├─ NotesFolder [id] ── parentId → NotesFolder
       │    └─ Note [id, folderId]
       │         ├─ editor blocks → media source / other note
       │         ├─ Pending / Base / Deleted [noteId]
       │         ├─ DriveIndex [noteId] → Google Drive .md [fileId]
       │         └─ Canvas [docId, id] → Elements → Connectors
       └─ FilesFolder [id] → Media metadata → local file / Drive source

Unless specified otherwise, app timestamps are numbers in milliseconds since the Unix epoch. Drive revision values are opaque strings; never compare them as integers. Positions are numeric sibling order. Missing optional fields and explicit null have different meanings: preserve the distinction during round trips.

3 · Note record and editor content

DocumentRecord is the browser/API document shape. Core fields are required for a normalized note; optional fields must survive supported round trips.

Field / keyType and presenceMeaning / example
idstring · coreStable logical note ID, for example doc-example. Use IDs compatible with endpoint validation: letters, digits, underscores and hyphens.
titlestring · coreNote title. This is readable note content, including when shown in a list.
folderIdstring · coreExisting Notes folder ID, default folder-notes.
contentProseMirror JSON object · core{type:"doc", content:[...]}; structured nodes, marks and attributes, not a plain HTML string.
createdAt, updatedAtnumber · coreCreation and last modification time in milliseconds.
trashedAtnumber | null · coreLegacy trash timestamp; null means active. New permanent deletion uses tombstones.
driveFileId, driveRevisionstring · optionalDecorated Drive mapping and confirmed remote version; excluded from encoded note metadata.
driveReadErrorstring · optionalRead failure information; never treat an unreadable note as a confirmed empty document.
questions, goalReferencesarray · optionalStructured note content. The source types are any[]; define and validate the supported entries before implementing these extensions. Do not invent a fixed schema from the array names.
goalStatusenum · optional compatibilityuncompleted | in_progress | completed | cancelled. Retained data does not authorize adding task-management UI.
goalUrgency, goalImportancehigh | low · optional compatibilityRetained fields, not mandatory product features.
trashedViastring · optional runtime fieldOrigin folder for legacy trash operations; used by the implementation even though absent from the declared DocumentRecord type.
forcedLanguagestring | null · optional legacyPreserve existing imported data where needed; do not build automatic translation or new forced-language controls.

Editor node structure

Field / keyType and presenceMeaning / example
typestringSchema node name such as doc, heading, paragraph or text.
contentnode[] · optionalRecursive ordered child nodes. A paragraph can contain text nodes.
textstring · text nodesThe actual text.
attrsobject · schema-dependentFor example heading {level:1}, media source attributes or a canvas reference. Validate against the editor schema.
marksarray of mark objects · optionalInline formatting/link marks; preserve supported type and attrs.
{
  "id": "doc-example",
  "title": "Research notes",
  "folderId": "note-folder-example",
  "content": {
    "type": "doc",
    "content": [
      {
        "type": "heading",
        "attrs": {
          "level": 1
        },
        "content": [
          {
            "type": "text",
            "text": "Research notes"
          }
        ]
      },
      {
        "type": "paragraph",
        "content": [
          {
            "type": "text",
            "text": "My observations about the source file."
          }
        ]
      }
    ]
  },
  "createdAt": 1793000000000,
  "updatedAt": 1793000001000,
  "trashedAt": null
}

4 · Folder tables

The server keeps dictionaries noteFolders[id] and folders[id]. The browser uses Notes workspaces in its local graph or folder:<id> records in Drive mode.

Field / keyType and presenceMeaning / example
id, namestring · coreStable folder ID and readable display name.
parentIdstring | null · normalized server shapenull only for the collection root; a child references a folder in the same tree. Browser workspace summaries may omit a root parent.
positionnumber · normalized server / Drive folderOrder among siblings; do not derive identity from ordering or name.
createdAt, updatedAtnumber · normalized server shapeMilliseconds.
trashedobject · optional server marker{at:number, folderId:string, viaFolder:string}; preserve deletion origin during legacy restore.
trashedAt, trashedVianumber | null; string · optional browser fieldsLocal workspace summaries also use these trash markers. Map them deliberately; they are not identical to the server marker object.
{
  "id": "note-folder-example",
  "name": "Sources",
  "parentId": "folder-notes",
  "position": 0,
  "createdAt": 1793000000000,
  "updatedAt": 1793000000000
}

5 · Browser tables and outgoing operations

Drive mode: account database

IndexedDB filesandnotes-drive-<userKey>, version 1, contains the object store records. Keys are external strings; the stored value is the record itself, not a {key,value} wrapper.

Key prefixValue shapeLifetime / meaning
note:<id>DocumentRecordCurrent local document; render before a background refresh.
base:<id>DocumentRecordConfirmed merge ancestor, needed for a three-way merge. Contains note content: browser only.
pending:<id>{id, document, baseDocument?, action?, token}Latest local operation. document is a full document or null for deletion; action may be restore; token is a fresh string.
deleted:<id>{id:string}Deletion tombstone; blocks stale fetches from restoring a deleted note.
sent:<id>stringOperation token already sent; supports duplicate suppression. It does not replace the confirmed base.
folder:<id>Folder recordCached Notes workspace.
folder-op:<sequence>{key,id,kind,body}Ordered outgoing action; kind is create, rename, move, delete or restore. Body depends on that action. Sequence is padded to 12 digits.
deleted-folder:<id>{id:string}Folder deletion tombstone.
meta:*Value depends on keyTechnical state including folder sequence, last sync time, upload result and legacy-import marker; no single universal meta-record type.

Write the local document and its pending operation in the same IndexedDB transaction. An acknowledgement may clear only the operation whose token still matches. Keep newer edits, failed operations and tombstones through reloads. Use account-specific browser locks and sync messages.

Browser-only mode and compatibility

The existing browser-only store opens paper_documents, version 4. It is still used when Drive mode is inactive and can later be imported into the Drive store. Local-first sync is disabled in its current constructor; these stores do not imply a second server sync protocol.

Object storeKey / indexValue shape
documentskeyPath idLocal document projection.
local_first_datasetskeyPath datasetId{datasetId, payload:string, updatedAt:number}. IDs include graph and doc:<id>; payload is serialized local-first state.
local_first_pending_updateskeyPath id; index datasetId{id,datasetId,payload,createdAt,deviceId?,tabId?}. IDs/payload are strings; time is numeric.
local_first_sync_cursorskeyPath datasetId{datasetId,seq:number,updatedAt:number}.
local_first_statekeyPath keyInternal state entries, including device identity; validate each supported key.
Reconstruction requirement: account isolation.

The browser-only database name currently has no userKey suffix. Do not interpret that as proof of account isolation. A rebuild must namespace all readable local notes and queues per account, migrate existing data safely and verify account switching. Changing this documentation does not fix the existing runtime limitation.

6 · Server JSON and technical Drive index

data.json is normalized through an explicit top-level whitelist. Adding a service key without updating that whitelist loses it on the next write.

Top-level keyShapePurpose
versionnumber, currently 1Envelope version.
files, googleDriveFilesobject dictionariesLocal and Drive media metadata respectively.
folders, noteFoldersobject dictionariesFiles and Notes folder metadata.
driveNotesobjectActivation, folder mapping, technical file index and allocated Drive IDs.
googleDriveConnectionobject | nullDrive authorization connection; see below.
notesFolderMigrationVersionnumberFolder migration marker.
noteStorageVersionoptional number; 1 after acknowledgementLegacy note-content transfer marker.
documentsobject, empty after migrationLegacy content source only; do not create new server notes here.
playlists, papersobject dictionariesExisting feature/compatibility collections; not a new required core workflow.
reportsarray, capped at 200Technical reports; never include note content or secrets.

Drive state

Field / keyType and presenceMeaning / example
activebooleanWhether Drive is the note sync backend.
folderId, folderName, emailstring · optional state metadataChosen Drive note folder and connection identity.
filesdictionary keyed by logical note IDValues restricted to the technical fields below after migration.
allocationsdictionary of note ID → Drive file IDStable allocated IDs for retry-safe creation.

Persisted Drive file entry

Field / keyType and presenceMeaning / example
fileIdstringRemote Google Drive file ID.
revisionstringOpaque version last observed/confirmed.
etagstring · optionalConditional request token.
hashstring · optionalSHA-256 fingerprint of normalized Markdown. A hash is metadata, not encryption.
deletedboolean · optionalKnown deleted mapping.
updatedAtnumber · optionalLast document modification time, milliseconds.
sourcestring · optionalOrigin marker, for example local or external.
lastTokenstring · optionalPush token used for duplicate retry detection.

This is the actual allowlist from driveFileMetadata. No title, content, baseContent, questions or goalReferences in newly persisted entries. Legacy entries may remain until a receipt-checked transfer is acknowledged; never delete them before verifying the browser import.

7 · Users, sessions and Drive connection

Field / keyType and presenceMeaning / example
users[userKey]User registry recordprovider:"google", sub, email, name, picture, userKey (strings); numeric createdAt, updatedAt, lastLoginAt; disabledAt:number|null. onboardedAt is optional, null until completed for new accounts.
userKey32-character stringCurrent Google key: first 32 hex characters of SHA-256 of issuer + ":" + sub. A data namespace, not a password or an authorization decision.
legacyOwnerUserKeystring | null · registry envelopeLegacy root ownership mapping; do not reassign old data just because an account is deleted.
sessions[sessionId]{user,updatedAt}Persisted authenticated user snapshot and numeric last activity; runtime also keeps refresh/persistence timing. Legacy string users may be read for compatibility, not created as a new login method.
googleDriveConnectionobject | null{email,refreshToken,connectedAt,uploadFolderId?}. Refresh token is encrypted at rest; time is numeric, remaining fields strings. No real token examples.

Google sign-in and Drive consent are separate flows. Store credentials only on the server; browser payloads expose connection status, not refresh tokens. Session IDs and OAuth secrets must never become fixture content in this package.

8 · Media, annotations and canvas

Media metadata

Local records are keyed by safe filename; Drive records by Google file ID. Sparse records are valid. A Drive upload returns the fuller shape below; a listing may derive display fields dynamically rather than persist each field.

Field / keyType and presenceMeaning / example
id, name, pathstring · source-dependentFor Drive upload records, ID is the Google file ID and path is the display filename. Local identity is a single safe filename.
source, routestring · source-dependentFor Drive: source:"gdrive", route:"gdrive:<fileId>". Keep source identity when inserting media in notes.
type, mimeType, size, mtimestring, string, number, number · optional metadataMedia kind/MIME type, byte count, modification time in milliseconds. Large bytes are not embedded in a record.
folderIdstring · optionalLibrary folder placement; distinguish it from the chosen remote Drive parent folder.
url, downloadUrl, thumbUrlstring · derived/source-dependentAuthenticated source endpoints; for example /google-drive/raw/<fileId> and /google-drive/thumb/<fileId>. Empty thumbnail URL is allowed.
playbackSeconds, lastPlayedMsnumber · optionalResume position in seconds and last played time in milliseconds.
annotationsarray · optionalEntries {id:string,time:number,text:string}; integer seconds, text currently limited to 200 characters.
notestring · optional media annotationSeparate per-media annotation stored on the server. This is not a top-level editor note; do not silently move editor content here.

Canvas document

Field / keyType and presenceMeaning / example
format, id, docId, titlestring · normalized canvasFormat is notesandfiles-canvas-v1; docId owns the canvas.
createdAt, updatedAtnumberMilliseconds.
elementsarrayEvery element has id,x,y,w,h. Shape: kind:"shape",shape,text,colour; note link: kind:"noteRef",docId,title; file link: kind:"fileRef",src,mediaKind,name. Coordinates/dimensions are numbers; other listed fields are strings.
connectorsarray{id,fromId,toId,fromSide,toSide,label,arrow}. Sides: auto/top/right/bottom/left; arrow: none/end/both.
Element enumsstringsShape: rectangle/rounded/ellipse/diamond; colour: neutral/blue/green/amber/rose/violet; mediaKind: audio/video/image.

Canvas JSON and media annotations are existing server-side content stores with their own privacy scope. They are not covered by a claim that all application data is browser/Drive-only. Do not add a duplicate server copy of editor note content to either collection.

9 · Drive Markdown and encrypted tickets

Drive file format

A note is a readable Markdown file with a single filesandnotes: JSON metadata line inside frontmatter. The encoder removes content, driveRevision, driveFileId and driveReadError from that metadata. Editor blocks have a Base64-encoded JSON node comment plus readable Markdown; this preserves supported structured nodes while allowing external edits.

---
filesandnotes: {"id":"doc-example","title":"Research notes","folderId":"note-folder-example","createdAt":1793000000000,"updatedAt":1793000001000,"trashedAt":null}
---

<!-- filesandnotes:block <Base64 JSON node; schematic placeholder> -->
# Research notes
<!-- /filesandnotes:block -->

The placeholder above is schematic, not an importable file. Base64 is not encryption. If a block’s Markdown changes outside the app, the decoder reparses that Markdown; malformed metadata/block markers produce an error rather than an empty overwrite.

Temporary encrypted packet

Field / keyType and presenceMeaning / example
Envelope vnumber, 1Envelope version.
Envelope iv, tag, dataBase64 stringsAES-256-GCM nonce, authentication tag and ciphertext. Random 12-byte nonce; server-derived encryption key.
Decrypted identity, status, body, result?Internal objectIdentity is a request fingerprint; status queued/done; body contains upserts[], deletes[], folders[]. Result appears after processing and may contain note content. Only the encrypted envelope is written to the ticket file.

A ticket is a random 32-character hex ID. Its account-scoped file is deleted when a completed result is consumed. Ticket acceptance confirms receipt, not a successful Drive write. The server can decrypt these packets; this is not end-to-end encryption. See 08 Security for risks and outstanding hardening.

10 · Data contract prompt and acceptance checks

Read prompt/data-structure.html before implementing stores or API payloads.
Create explicit shared record schemas for the chosen scope. Keep logical note IDs, folder IDs, userKey, Drive file IDs and revision strings distinct. Use synthetic fixtures only.
Implement account-scoped browser stores; do not reproduce the legacy browser-only database naming limitation. Preserve optional compatibility fields without creating new product workflows.
Keep note titles, bodies, questions, references and merge bases in the browser and optional user Drive. Persist only the documented technical Drive allowlist on the server. Keep canvas/media annotation storage explicit.
Commit document + pending operation atomically. Clear a queued version only after its matching acknowledgement. Keep tombstones and failed work after reload. Define migrations before changing schema versions.
Verify record round trips, folder integrity, two-account isolation, stale acknowledgements, retry-safe Drive creation, offline reload, deletion races and legacy import acknowledgement. Do not erase user data to make a migration pass.
Report schema decisions, actual test evidence and any unsupported extension fields in build-status.md.
  1. Round-trip the synthetic note and folder without changing IDs, nulls, block order or supported attributes.
  2. Reject invalid IDs, folder cycles and cross-account access; test browser-only and Drive modes separately.
  3. Reload offline with pending work, then replay it. A stale token must not clear a newer edit.
  4. Inspect new server files after saving a unique note sentence and title: neither may appear as readable note content. Inspect technical Drive entries against the allowlist; canvas/media fixtures use their separate scope.
  5. Verify supported Markdown blocks and external edits, then verify that malformed data cannot overwrite a good note with a blank one.
  6. Use 06 Verification for the broader product scenarios. Use A01–A12 and synthetic fixtures in 15 Acceptance & Fixtures. These are checks to perform in the generated app, not claims that this documentation edit ran them.