FileLoveNote / build packageKit revision · 28 October 2026

02 / 15 · Solution Design

How the components work together

A Bun server serves HTML and APIs. The browser saves notes locally first. Google Drive is the optional user-owned storage for synchronization.

For the user interface’s appearance, use 09 Design: visual layouts, colour tokens, typography, controls and mobile behaviour.

Detailed contracts

Use 11 API Contracts, 12 Failure & Recovery, 13 Markdown Compatibility, 14 Setup & Operations and 15 Acceptance & Fixtures for exact build decisions. Source observations are not automatically target behavior; known limitations explicitly marked for correction must be fixed in the new build.

Stack and implementation boundaries

This package reconstructs the existing application: Bun, CommonJS server modules, HTML rendering functions, TypeScript custom elements and ProseMirror. This specific architecture takes priority over the general preference for React. Switching to another framework requires a separate design.

LayerChoiceContract
HTTPBun.serve + custom routerDo not assume Express. Routes, services, request context and rendering are separate.
BrowserTypeScript/ESM + custom elementsBun serves browser modules; no separate frontend build is required.
EditorProseMirror directlySchema, input rules, history, tables and Markdown parser/serializer.
StorageIndexedDB + your own DriveNo SQL or persistent readable note text on the server.
Server dataJSON + filesAccounts, sessions, media, canvas and technical metadata per account; use atomic writes.
Testsbun test + BrowserOS NeoTest doubles for external services; report real integration checks separately.

The repository uses prosemirror-*, orderedmap, dotenv, google-auth-library, fflate and loro-crdt. The 28 October baseline pins 17 runtime and two development dependencies. Password hashing and its helper script have been removed. Use the exact manifest and lockfile in Setup & Operations; record any deliberate change with new verification evidence. Do not automatically add a second sync protocol just because a dependency exists.

Suggested project structure

filelovenote/
  server.js                  # bootstrap and start
  src/app/                   # context, request scope, service graph
  src/http/                  # router, auth, request body, responses
  src/config/                # environment and feature flags
  src/session/               # sessions
  src/accounts/              # accounts and isolation
  src/data/                  # technical metadata and media data
  src/routes/                # endpoints by feature
  src/services/              # Drive, sync, media, search, migration
  src/views/                 # HTML, themes, navigation, dialogs
  client/paper/              # editor, stores, Drive picker, canvas
  tests/                     # unit and integration tests
  prompt/                    # this transferable build package
  .env.example               # names, explanations, no secrets
  bun.lock                   # locked dependencies

APP_DATA_DIR/                # outside the checkout in deployment
  users.json, sessions.json
  users/<userKey>/           # account-scoped technical data and media
  ...                       # no new readable note files

Data model and ownership

Use 10 Data Structure for the field dictionary, logical table keys, relationships, synthetic records and compatibility limits.

ObjectFields / contentsStorage location
Noteid, title, folderId, content (ProseMirror JSON), createdAt, updatedAt; optional questions and source referencesIndexedDB; Markdown with metadata in Drive. Titles also count as note content.
Local outgoing operationAccount, id, operation, local revision token, payload, confirmed base and Drive revisionIndexedDB; clear only after this exact version is confirmed.
Drive indexfileId, revision, etag, hash, deleted, updatedAt, source, lastTokenThe server may retain only these technical fields.
Folderid, name, parentId, timestamps; deletion origin for legacy trash itemsBrowser in local mode; account-scoped folder metadata and Drive operations during sync.
Media fileSafe id, name, source, type, size, progress, tags, annotations, transcript statusOwn media source; account-scoped metadata on the server. Keep this separate from note text.
CanvasCards, positions, connections, source idsOwn canvas store per account; the note storage contract does not silently change canvas storage.
Sync ticketId, encrypted payload/result, processing statusTemporary encrypted server packet; delete when the Drive result is retrieved.

Read and write flows

1 · EditorSave current text and the operation in IndexedDB.
2 · ForwardBatch changes; the server temporarily stores an encrypted packet.
3 · Your DriveWrite Markdown; confirm the revision to the browser.
  1. Open the local list and selected note first. Then refresh in the background. Without a cache, the initial loading state may end only when fetching finishes or an error is known.
  2. Save edits locally, with a durable queue and base version. Work without Drive remains local.
  3. Automatically send batches after a change, on return and on reconnection. A visible tab checks periodically, currently every twelve seconds. The button triggers the same flow.
  4. A push may return a result directly or, with station: true, first return { queued: true, ticket }. A ticket confirms receipt by the server, not yet storage in Drive.
  5. Encrypt temporary packets with AES-256-GCM, a random nonce and a server secret; decrypt only in memory. The current implementation derives the key from a secret. This is not end-to-end encryption: the server can process the packet.
  6. Write at most eight notes to Drive at once. Create required folders first and delete folders last. Retrieve the outcome for each item; remove only the exact confirmed local operations and clean up the ticket.
  7. New text written during a push keeps its own revision and is sent afterward. BroadcastChannel and an account-scoped lock coordinate tabs.

Conflicts, outages and deletion

  • Compare the local version, confirmed base and current Drive version. Use the etag when writing. A changed side takes priority over an unchanged side; independent text changes may be merged. Only incompatible changes to the same text create a controlled conflict copy.
  • A 503, limit, lost response or missing list entry does not prove deletion. Check missing Drive files by id; do not delete an unconfirmed new local note.
  • Unreadable Markdown or an HTML error page must not overwrite valid local text or a merge base. Show the problem for that note.
  • An old selection or load operation must not populate the editor after a new selection. Use an increasing load token. Deletion markers and queue operations use the same lock, so stale responses cannot bring a note back.
  • Delete permanently removes the explicitly selected object after one confirmation. Removing a source reference keeps its original file. Library metadata removal and original-file destruction are distinct actions; apply the deletion scope table. Existing trash items retain separate restore/purge behavior; do not introduce a new general soft-delete rule.
  • On pagehide, a bounded keepalive request may send the current editor content. The current web client uses at most 48 KB per request and 60 KB in total. This is extra protection; IndexedDB and retries are still needed.
  • Migration is needed only when legacy data exists: import, save durably, read back and confirm before cleaning up the source. A read or status request must not start migration.

API boundaries

Route familyContract
GET /notes; GET /filesWorkspaces and shell; / redirects to /notes.
POST /auth/google; GET /auth/google/callback; POST /logoutStart Google OAuth, validate the callback, create and end sessions.
GET /api/notes/storageDistinguish configured, connected and active note storage; show the connected Drive account.
POST /api/drive-notesBody { revisions: { [id]: revision } }; response { documents, revisions, hasMore }. At most 40 changed/missing notes per batch.
POST /api/drive-notes/pushBody { upserts, deletes, folders, station? }; directly { notes, folders } or { queued: true, ticket }. Per-item result with a revision or conflict.
GET /api/drive-notes/push-ticket?ticket=…While processing: { queued: true, ticket }; then the processing result. Account-scoped.
GET /api/notes; GET|POST /api/notes/:idExisting note routes as a compatibility layer. Without Drive, no persistent server writes.
GET /api/note-folders; GET|POST /api/foldersNote folder tree and file folders, respectively; browser mode works locally.
POST /searchNotes and folders only; { items, total }. Current browser documents may be sent temporarily.
GET /api/google-drive/status; GET /api/google-drive/listConnection status and picker; includeAll=true for all types, with pagination.
POST /api/library-items/delete; GET /api/trashTargeted permanent deletion; manage legacy trash separately.
GET /stream/:name; GET /raw/:name; GET /media/:name; GET /thumb/:nameByte-range playback, inline viewing, downloads and thumbnails.
POST /api/notes/:id/ai; GET|PUT|DELETE /api/notes/:docId/canvas/:canvasIdExplicit AI action and canvas; P05.

This is a route overview. 11 API Contracts owns concrete item fields, examples, validation, errors, pagination and retry behavior; test browser/server against it. 12 Failure & Recovery owns state transitions, deletion scope and recovery. The legacy GET /api/drive-notes currently returns 409. Do not give it a new bulk download feature.

Media and integrations

Support audio, video, images, PDF and EPUB. Streaming responds correctly to normal, open-ended and suffix byte ranges with 206, and to invalid ranges with 416. Save playback progress per account. ffmpeg creates thumbnails; without the binary, show a placeholder.

The current local vault upload route is not a universal upload destination. Send active uploads to Google Drive when connected; without Drive, clearly show that uploading is unavailable. Test chunk order, interrupted uploads, name reservations and two simultaneous files with the same name. Drive media upload is not a Google HTTP batch; use resumable uploads. Batched note sync is an application request with bounded Drive writes.

Google sign-in and Google Drive use separate OAuth configurations. Drive requests read access for the picker and write access for app files. Without configuration, show a clear connect button or unavailable status. The inline Drive picker, source insertion and annotation flow are core FR-17 in P03/P04; Gemini, YouTube, canvas and Whisper are separate extensions and must not block the core.

Configuration and execution

# Example for a new local reconstruction; do not copy an existing .env.
PORT=3000
APP_DATA_DIR=./runtime-data
SESSION_SECRET=<create-your-own-long-random-secret>
SESSION_COOKIE_SECURE=false
FOLDERS_ENABLED=true
LOCAL_FIRST_ENABLED=false

# Required for sign-in: your own Google OAuth client
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=

# Optional: separate Google Drive OAuth
GOOGLE_DRIVE_CLIENT_ID=
GOOGLE_DRIVE_CLIENT_SECRET=
GOOGLE_DRIVE_REDIRECT_URI=

# Only for selected extensions
GEMINI_API_KEY=
GEMINI_MODEL=
YOUTUBE_API_KEY=

LOCAL_FIRST_ENABLED is a legacy server sync flag; local IndexedDB storage must work without it. The examples are choices for a new build, not claims about production settings. Follow the pinned setup and OAuth instructions, including fail-closed production configuration and the backup/restore matrix. After implementation, start with bun install --frozen-lockfile and bun run dev; run bun run test. In production, use HTTPS, secure cookies, Google OAuth and a data directory outside the checkout.