Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# AGENTS.md

Guidance for coding agents in this repository. `DEVELOPMENT.md` is the full
developer guide; this file holds what an agent needs first.

## Commands

Node.js 24.21 (`.node-version`) and the Yarn 3 release in `.yarn/` (any global
`yarn` starts it). Electron 44 downloads its binary on first use
(`node -e 'require("electron")'`).

| Task | Command |
| --- | --- |
| Install | `yarn install --immutable` |
| Build everything (development) | `yarn build-dev` |
| Build only the UI / only Electron | `yarn build-angular-dev` / `yarn build-electron-dev` |
| Start the desktop app | `yarn start` (profile `.dev-profiles/dev`) |
| Start the browser client | `yarn build-angular-dev && yarn browser` |
| Unit tests (renderer backup, profiles) | `yarn test` |
| Storage service tests | `yarn test-storage` |
| Storage service type check | `yarn workspace kc_storage typecheck` |
| Desktop end-to-end tests | `yarn build-dev && yarn e2e` |
| Browser end-to-end tests (Chrome) | `yarn build-angular-dev && yarn e2e-browser` |
| Packaged app test | `yarn package-local && yarn e2e-packaged` |
| Format changed files | `yarn prettier --write <files>` |

Run one test file with `node --test e2e/inbox.e2e.mjs` (or
`src/kc_storage/test/inbox.test.ts`). Select one test with
`--test-name-pattern="<part of the name>"`. End-to-end tests must run one
at a time (`--test-concurrency=1`).

The end-to-end tests drive the built output. Build again after a source
change, or the tests run old code.

## Architecture

Three Yarn workspaces and two shared folders:

| Part | Owns |
| --- | --- |
| `src/kc_storage` | The storage service: projects, sources, the inbox, managed files, preferences, validation, SQLite persistence (`node:sqlite`), backup and restore, browser sessions, and serving the built UI. Plain Node.js 24 with type stripping; no build step. |
| `src/kc_angular` | The UI (Angular 14, PrimeNG 14). Runs in Electron and in a normal browser. |
| `src/kc_electron` | The desktop shell: windows, file access by path, watched folders, embedded browser (`WebContentsView`), thumbnails, Save as PDF, and the local chat server (`src/local`). Starts and stops the storage service. |
| `src/kc_contracts` | Storage API types and record mapping. Imports nothing from the apps. |
| `src/kc_shared` | Shared models and default settings. No Angular or Electron imports. |

Key paths through the code:

- **One library API for both clients.** The UI writes every change through
`StorageService` (`kc_angular/.../services/ipc-services/storage.service.ts`)
to the storage service. Writes are idempotent PUT or DELETE by client
ID, run in order, and wait in the queue while the connection is not
active. Uploads go to `POST /v1/assets`, and sources refer to the
returned asset ID.
- **The inbox is sources with `projectId: null`.** A transfer to a project
is one PUT that sets the project ID. Library backups (format version 2)
include the inbox.
- **Platform capability interfaces** in `kc_angular/src/app/platform/`
(`Platform`, `SettingsStore`, `WindowControls`, `NativeFiles`,
`ManagedFiles`, `WebsitePdf`) and `BackendService` have a desktop and a
browser implementation each. `app.module.ts` selects them once with
`isDesktop()` (the Electron preload bridge exists). New desktop-only
behavior goes behind one of these interfaces or `Platform.has()`, with a
clear unavailable state in the browser. `ElectronIpcService` is the
remaining direct desktop adapter.
- **Desktop authorization:** the storage service gets a random bearer token
per app instance; Electron gives it to the renderer through IPC.
- **Browser authorization:** `yarn browser` prints a one-time launch link.
The page exchanges the code for an `HttpOnly` session cookie, and writes
need a CSRF header and the service's own Origin. `BackendService` tracks
the connection state; the session dialog blocks the app while the
session is not active.
- **Per-instance servers:** each app instance starts its own chat server
and storage service on ephemeral loopback ports.

## Rules

- Never use or change the user's normal application data. Use
`KC_PROFILE_DIR=<scratch dir>` for desktop runs and a scratch
`--data-dir` for the storage service and `yarn browser`. Tests create
their own folders under `e2e/.output/`.
- Automated tests must not take over the user's screen or focus. Desktop
tests run the Electron window invisible and inactive
(`KC_HIDDEN_WINDOW=1`, set by `e2e/lib.mjs`; `KC_E2E_SHOW=1` shows it).
Chrome tests run headless.
- Render or capture the UI only where a permanent test needs it. Remove
temporary checks that an agent adds for its own observation (screenshots
to look at, probe scripts, debug logging) before the work is finished.
- Do not commit `KNOWLEDGE_REIMAGINED_PLAN.md` or
`Resources/tiktoken_bg.wasm`; they are the user's untracked files.
- Do not push, publish, merge, or sign releases unless asked.
- Reports and documentation use ASD-STE100 Simplified Technical English.
- Open follow-up work is in `docs/follow-ups.md`; milestone records are in
`docs/`.
7 changes: 7 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

The guidance for all coding agents is in AGENTS.md:

@AGENTS.md
104 changes: 81 additions & 23 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ before you go offline: `node -e 'require("electron")'`.
| Part | Location | Runs in | Owns |
| --- | --- | --- | --- |
| Frontend | `src/kc_angular` | Electron renderer or a browser | Navigation, presentation, editing, temporary UI state, API calls |
| Backend (storage service) | `src/kc_storage` | Separate Node.js process | Projects, sources, managed files, validation, persistence, backup and restore, browser sessions, browser UI files |
| Desktop | `src/kc_electron` | Electron main process | Windows, native file access by path, watched folders, thumbnails and file icons, embedded browser, drag-out, backend start and stop. Also the chat server (for now). |
| Backend (storage service) | `src/kc_storage` | Separate Node.js process | Projects, sources, the inbox, managed files, browser preferences, validation, persistence, backup and restore, browser sessions, browser UI files |
| Desktop | `src/kc_electron` | Electron main process | Windows, native file access by path, watched folders, thumbnails and file icons, embedded browser, Save as PDF, drag-out, backend start and stop. Also the chat server (for now). |
| Contracts | `src/kc_contracts`, `src/kc_shared` | Shared | Data types, record mapping, default settings. No Angular or Electron imports. |

The frontend reaches platform functions only through small capability
Expand All @@ -56,16 +56,33 @@ interfaces in `src/kc_angular/src/app/platform/`:
| Interface | Desktop | Browser |
| --- | --- | --- |
| `Platform` | All features | Library features; desktop-only actions are hidden or disabled with a reason |
| `BackendService` | IPC addresses, bearer tokens | Same origin, session cookie, CSRF header |
| `SettingsStore` | Electron settings file | `localStorage` |
| `BackendService` | IPC addresses, bearer tokens | Same origin, session cookie, CSRF header, session state |
| `SettingsStore` | Electron settings file | Storage service (`/v1/preferences/browser-settings`) |
| `WindowControls` | Minimize, maximize, zoom | Not available |
| `NativeFiles` | Original file path, copy by path, open in default app | Not available |
| `ManagedFiles` | `blob:` view, default app | Same-origin URL with filename, new tab |
| `WebsitePdf` | Save a website as PDF | Not available |

Both clients import files the same way: the user selects a `File`, the
frontend uploads its bytes (`POST /v1/assets`), and the source stores the
asset ID. IDs come from `crypto.randomUUID()`.

Both clients write through one ordered queue in `StorageService`. Each
write is an idempotent PUT or DELETE by ID. A write that the service
refuses because of the session (401, 403), or that does not reach it, stays
in the queue with all later writes until the connection is active again.
The page warns before it closes while writes are queued.

The inbox is sources with no project (`projectId: null`). A transfer to a
project is one PUT that sets the project ID, so it cannot copy or lose the
source. The inbox list removes an entry only after that write is saved.

Remaining direct desktop calls: `ElectronIpcService` (embedded browser,
thumbnails, file icons, local paths, show in folder, watched folders),
`DragAndDropService` (drag-out), the display and import settings pages, and
the chat server. Each one is unreachable in the browser or behind a
`Platform` check.

Designs: [docs/storage-service.md](docs/storage-service.md),
[docs/desktop-storage-completion.md](docs/desktop-storage-completion.md),
[docs/browser-library.md](docs/browser-library.md).
Expand All @@ -74,14 +91,18 @@ Designs: [docs/storage-service.md](docs/storage-service.md),

| Data | Owner | Location in a profile |
| --- | --- | --- |
| Projects, sources, topics, metadata | Storage service | `data/library/library.sqlite` |
| Projects, sources, inbox, topics, metadata | Storage service | `data/library/library.sqlite` |
| Copies of imported files | Storage service | `data/library/assets/` |
| Chat history, inbox, current project, UI preferences, favicon cache | Renderer | `userData` (local storage) |
| Browser client settings | Storage service | `data/library/library.sqlite` (not in backups) |
| Chat history, current project, UI preferences, favicon cache | Renderer | `userData` (local storage) |
| Settings | Electron | `settings/knowledge.settings.json` |
| Extracted-text cache, API key file | Electron chat server | `data/storage/sources/`, `data/openai.encrypted` |

The app does not read or write the old local-storage project keys
(`kc-projects`, `<projectId>`, `ks-<id>`). Use the migration to import them.
An inbox that an earlier version kept in local storage (`ingest-queue`)
moves to the storage service at the next start, once. Entries that the
service already has stay unchanged.

## Instances and local servers

Expand Down Expand Up @@ -163,23 +184,27 @@ yarn browser

`yarn browser` starts the storage service with the built UI and prints a
one-time launch link, for example
`http://127.0.0.1:53124/#launch=...`. Open it in Chrome. Press Enter in the
`http://127.0.0.1:45120/#launch=...`. Open it in Chrome. Press Enter in the
terminal for a new link. Press Ctrl+C to stop.

Options: `--data-dir <dir>` (default `.dev-profiles/browser/library`),
`--web-root <dir>` (default `src/kc_angular/dist/main`), `--open` (macOS:
open the default browser).
`--web-root <dir>` (default `src/kc_angular/dist/main`), `--port <n>`,
`--session-idle-minutes <n>`, `--open` (macOS: open the default browser).

The port comes from the library path (41000-48999), so one library always
opens at the same address. The browser keeps UI preferences per address.
If the port is in use, the launcher stops and asks for `--port`.

What works in the browser: projects, file upload (PDF, text, and other
files), the Document view for PDF, text, and images, topics and metadata,
search, opening managed files in a new tab, library export and restore,
chat and preferences backup (browser local storage).
search, opening managed files in a new tab, preview of PDF, text, and
images, the inbox, library export and restore, settings, logout.

Desktop only (hidden or disabled in the browser, with a reason): saving
websites and the example websites, chat, the built-in browser, watched
folders and extension settings, window controls, opening files in other
apps, showing files in Finder, file thumbnails and file icons, dragging
files out.
websites and the example websites, Save as PDF, chat, the built-in browser
(website Preview opens a new tab instead), watched folders and extension
settings, window controls, opening files in other apps, showing files in
Finder, file thumbnails, file and website icons, dragging files out.

### Browser session

Expand All @@ -191,7 +216,15 @@ files out.
a browser marks as cross-site or same-site (another local port) are
refused.
- A session ends after 30 minutes without requests, after 12 hours, on
`DELETE /v1/session`, or when the service stops.
`DELETE /v1/session`, or when the service stops. Each session response
carries `X-Knowledge-Session-TTL`, so the page knows when it ends.
- When the session ends or is refused, a dialog blocks the app. Unsaved
changes stay in the tab. Press Enter in the launcher terminal, paste the
new link, and select Continue (or open the link in another tab and select
Check again). The queued changes are then saved once, in order.
- Log out (title bar) saves queued changes first, or asks before it
discards them. It ends the session, removes library caches from the
page, and reloads the page without library data.
- The bearer token is never printed or sent to the browser. The launch
code appears once in the terminal and in the first URL fragment.
- This is a local, single-user setup. It is not a hosted deployment.
Expand Down Expand Up @@ -280,8 +313,8 @@ application backup.

| Backup | Contains | Does not contain | Restore |
| --- | --- | --- | --- |
| Library (`knowledge-library-<date>.tar`) | Projects, sources, topics, metadata, copies of imported files (SHA-256 for each) | Chat history, inbox, UI preferences, settings, API keys, extracted-text cache | Settings > Backup > Restore Library, or `yarn storage restore` |
| Chat and preferences (`knowledge-backup-<date>.json`) | Renderer local storage: chat history, inbox, UI preferences | Projects, sources, files, settings | Settings > Backup > Restore |
| Library (`knowledge-library-<date>.tar`, format version 2) | Projects, sources, inbox entries, topics, metadata, copies of imported files (SHA-256 for each) | Chat history, UI preferences, settings, API keys, extracted-text cache | Settings > Backup > Restore Library, or `yarn storage restore`. Version 1 backups (no inbox) also restore. |
| Chat and preferences (`knowledge-backup-<date>.json`) | Renderer local storage: chat history, UI preferences | Projects, sources, inbox, files, settings | Settings > Backup > Restore |

### Restore a library in the app

Expand Down Expand Up @@ -334,7 +367,13 @@ Stop the app first. The command uses the same checks as the app.
| `yarn e2e-browser` | The browser client in Google Chrome, no Electron (below) | Google Chrome, `yarn build-angular-dev` first |

The end-to-end tests start the real app with new, empty profiles in
`e2e/.output/<run>/`. They never use your normal profile.
`e2e/.output/<run>/`. They never use your normal profile. The desktop
window stays invisible and never takes focus (`KC_HIDDEN_WINDOW=1`, set by
`e2e/lib.mjs`). To watch a run: `KC_E2E_SHOW=1 yarn e2e`. Chrome runs
headless.

Run one file with `node --test <file>`; select one test with
`--test-name-pattern="<part of the name>"`.

- `e2e/workflow.e2e.mjs`: create a project, save a link (local fixture site),
import a PDF and a text file, annotate, search, restart, delete the
Expand All @@ -343,8 +382,12 @@ The end-to-end tests start the real app with new, empty profiles in
restore it into a second profile through Settings > Backup, and show the
PDF there.
- `e2e/embedded-browser.e2e.mjs`: opens a website source in the desktop
Browser tab (`WebContentsView`), checks its URL, resizing, and navigation
state, and checks that closing removes it.
Browser tab (`WebContentsView`), follows a link to a second page, goes
Back and Forward with the app's buttons, checks that the view stays inside
the window after a resize, and checks that closing removes it.
- `e2e/inbox.e2e.mjs`: an inbox in renderer local storage (earlier
versions) moves to the storage service once, with its file copied, and
inbox entries survive restarts without duplicates.
- `e2e/isolation.e2e.mjs`: two profiles at the same time get different chat
and storage addresses and tokens. Each server rejects the other
instance's requests. A second start of the same profile exits.
Expand All @@ -368,7 +411,18 @@ The end-to-end tests start the real app with new, empty profiles in
remain, opens the managed files in new tabs, exports the library, and
restores it through the UI into a second library. It also checks
requests without a session, reads and writes from another local site,
and that the launcher does not print the bearer token.
and that the launcher does not print the bearer token. A file stays in
the inbox through export and restore. Console errors and page errors
are checked at each stage.
- `e2e/browser/inbox.e2e.mjs`: inbox entries and a changed setting survive
a restart on a different port; a transfer that the service refuses
(broken session) stays in the inbox and is saved once after a new
session; a repeated transfer adds nothing; Save as PDF is disabled with a
reason.
- `e2e/browser/session.e2e.mjs`: a session that ends from inactivity blocks
changes and keeps an unsaved edit, which completes once after a new
session; logout saves a slow queued write first, then the page has no
access, no library text, no session cookie, and no library caches.

Test files are in `e2e/fixtures/`. To delete old test output, delete
`e2e/.output/`.
Expand All @@ -391,6 +445,10 @@ curl -s -X POST http://127.0.0.1:7777 -d '{"op":"close"}'
- Library restore works only into an empty library.
- Chat history, UI preferences, and settings are separate from the library
and its backup.
- Browser UI preferences other than settings (theme, table layout) belong
to the browser address. They survive restarts only on the same port.
- Resizing the desktop window reopens the built-in browser at the source
URL. Its Back and Forward history is lost.
- A file source that had no original file at migration has no managed copy.
It still depends on its original path.
- Packaging is verified only for macOS arm64 (unsigned). A pinned runtime
Expand All @@ -399,7 +457,7 @@ curl -s -X POST http://127.0.0.1:7777 -d '{"op":"close"}'
- Signed and notarized releases are not verified. Signing must also sign
the bundled Node.js binary.
- AI chat with a real API key is not verified.
- Electron 26 and Angular 14 are outside their supported versions.
- Angular 14 is outside its supported versions.

Follow-up work is in [docs/follow-ups.md](docs/follow-ups.md).

Expand Down
13 changes: 12 additions & 1 deletion docs/browser-library.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ platform is chosen once at startup: desktop if the preload bridge exists.
| --- | --- | --- | --- |
| `Platform` | Which optional features exist | All features | Library features only |
| `BackendConnection` | Address and request authorization for the library API (and chat) | IPC info, bearer token | Same origin, session cookie, CSRF header |
| `SettingsStore` | Load, save, and default settings | Electron settings file (IPC) | `localStorage`, shared defaults |
| `SettingsStore` | Load, save, and default settings | Electron settings file (IPC) | Storage service preference document, shared defaults |
| `WindowControls` | Minimize, maximize, zoom | IPC | Not available (controls hidden) |
| `NativeFiles` | Original path of a selected file, copy a file by path, open in default app, show in folder | IPC | Not available |
| `ManagedFiles` | URL to show or open a managed file | `blob:` URL from an authorized fetch | Direct same-origin URL with the filename |
| `WebsitePdf` | Save a website as PDF | Hidden window and `printToPDF` (IPC) | Not available, with a reason |

Other changes:

Expand Down Expand Up @@ -107,6 +108,16 @@ for a hosted service.
run scripts with the application's origin.
- The browser shows PDFs with its own viewer.

## Changes after review (2026-10-08)

- The inbox and browser settings moved to the storage service. The launcher
keeps one port per library.
- `BackendService` tracks the connection state. A session dialog blocks the
app when the session ends; queued writes are saved after a new session.
The title bar has Log out.
- Save as PDF, website Preview, file Preview, Chat, Show in files, and
website icons have a browser behavior or a clear unavailable state.

## Tests

- Service: sessions (launch code single use and expiry, cookie flags, CSRF
Expand Down
Loading