# AGENTS.md

## Task Completion Requirements

- Run commands from the repository root.
- Before considering a code change complete, `pnpm lint:ci` and
  `pnpm test:ci` must pass. `lint:ci` includes workspace validation,
  TypeScript (`tsgo`) project-reference checks, and Biome.
- Run `pnpm build` when changing browser bootstrapping, build configuration,
  dependencies, environment variables, themes, or production-only behavior.
- Run the relevant Playwright suite only for the final PR update, before a
  release, or when the user explicitly asks. For user-visible UI, routing,
  editor behavior, browser storage, persistence, or cross-tab changes, add or
  update the relevant Playwright coverage but defer executing it during normal
  iteration. Use `pnpm e2e:ci` for the full E2E and component suite, or a
  narrower filtered command while iterating when execution is explicitly
  requested.
- Every feature intended for release must include a high-quality Playwright E2E
  test that proves its user-visible behavior. Component tests may supplement
  this coverage but do not replace the released workflow test. A manual smoke
  test also does not replace committed regression coverage.
- Before every release, use `playwright-cli` against the release candidate and
  manually exercise the affected feature plus the local-first persistence smoke
  path. A release is not ready based only on automated test results.
- Before the final PR update and before any release, `pnpm local-ci-check` must
  pass. It runs every root script ending in `:ci`, including Playwright.
- When working on a non-main branch, end every turn by committing completed
  changes and creating or updating the PR for that branch.
- Documentation-only changes do not require the code suites. Verify the
  documented paths and commands instead.
- Never hide a failing check. Distinguish failures introduced by the change
  from existing failures and report the exact command and error.

## Project Snapshot

Learnitive is a local-first collaborative browser development platform built on the reusable InteractiveWeb UI/design base. It combines a browser IDE, local project storage, universal build/run workflows, secure per-user Linux sandboxes, Preview, collaboration, local/cloud AI, Templates, Learn, and first-class LaTeX/PDF workflows.

Use `interactive-*` for reusable InteractiveWeb CSS/DOM/design infrastructure and `learnitive-*` only for genuinely Learnitive-specific UI/runtime concepts. Do not introduce `learnitive-*`. Canonical product persistence/protocol identity remains `learnitive:` and `web+learnitive`; service configuration remains `LEARNITIVE_*`. The separate TeXise deployment and `/etc/texise/*` are legacy sibling-product infrastructure and must not be modified except where an Learnitive isolation check intentionally verifies they remain untouched.

## Core Priorities

1. Protect user data. Never turn a read, parse, permission, or storage failure
   into deletion, empty content, or a misleading success state.
2. Preserve Markdown fidelity. WYSIWYG parsing and serialization must not
   silently discard or reinterpret source content.
3. Keep local-first behavior predictable across reloads, tabs, IndexedDB, and
   Native FS.
4. Prefer maintainable package-level solutions over one-off fixes, while
   keeping changes small and explicit.

When priorities conflict, choose durable data, correctness, and a clear
recovery path over convenience.

## Maintainability

- Before adding functionality, inspect the owning package and lower layers for
  existing behavior that should be extended or shared.
- Before writing ProseMirror position, range, or transaction math, inspect
  `packages/js-lib/prosemirror-editor/src/pm-utils` and official ProseMirror
  utilities. Prefer an existing helper; otherwise keep unavoidable math behind
  a typed helper, promoting it to `pm-utils` when its semantics are reusable.
- Put behavior where the concept is owned, not where the first caller happens
  to need it. Keep local mechanism close to its owner, and move reusable policy
  or meaning behind a typed API at the lowest appropriate layer.
- Duplicate business rules or state transitions across files are a code smell.
  Extract shared behavior into the lowest workspace allowed by the dependency
  graph (`js-lib`, `shared`, `platform`, `ui`, or a service), with a typed public
  API and focused tests.
- Refactor existing modules and package APIs when that produces one coherent
  implementation. Do not preserve a poor boundary by adding a parallel local
  code path or a one-off workaround.
- Keep the abstraction proportional to demonstrated reuse: remove real
  duplication, but do not create speculative frameworks for a single use case.

## Workspace Model

The PNPM workspace is split by architectural role. The authoritative dependency
rules are the `learnitiveWorkspaceConfig.allowedWorkspaces` fields in each
`packages/<workspace>/package.json`; `pnpm custom-validation` enforces them.

| Workspace | May depend on | Role |
| --- | --- | --- |
| `tooling` | any workspace | Build entry, tests, Storybook, scripts |
| `core` | core, platform, ui, shared, js-lib, tooling | App and business logic |
| `platform` | shared, js-lib | Browser/platform implementations |
| `ui` | ui, shared, js-lib | Presentational components |
| `shared` | shared, js-lib | Cross-layer contracts and app utilities |
| `js-lib` | js-lib | App-agnostic libraries |

Two validator exceptions are intentional: `@learnitive/types` may reference any
workspace for type contracts, and `@learnitive/test-utils` may be used as a dev
dependency anywhere. Do not use either exception to move runtime logic across
layers. Do not widen an allowed-workspace list to make an import pass; first put
the behavior in the correct layer.

Package metadata also declares `learnitivePackageConfig.env` (`browser`, `nodejs`,
or `universal`) and `kind`. Browser packages cannot gain Node runtime
dependencies, universal packages must remain universal, and service dependency
order is `service-platform` -> `service-core` -> `service-ui`.

## Package Roles

- `packages/tooling/browser-entry`: Vite entry point. Installs build globals,
  translations, theme, Sentry, root emitter, Jotai store, and services before
  rendering React.
- `packages/core/app`: application pages, layouts, and React composition.
- `packages/core/editor`: Learnitive editor service, save lifecycle, and
  ProseMirror integration.
- `packages/js-lib/prosemirror-editor` and `prosemirror-plugins`: reusable editor
  schema/plugins and Markdown parsing/serialization behavior.
- `packages/core/service-core`: business services with platform capabilities
  supplied through typed service slots and DI.
- `packages/core/initialize-services`: the composition root that wires core
  services to browser platform implementations and mounts the container.
- `packages/platform/service-platform`: routers, databases, file-storage
  adapters, and platform error handlers. It must not import from `core` or `ui`.
- `packages/shared/commands`: serializable command definitions and validation.
  Implementations belong in `packages/core/command-handlers`.
- `packages/shared/ws-path`: canonical parsing, validation, and normalization of
  workspace paths.
- `packages/shared/root-emitter`: typed app events and the explicit cross-tab
  event allowlist.
- `packages/ui/base-ui`, `ui-components`, and `ui-misc`: reusable UI without
  core business behavior. `base-ui` holds the Base UI-backed shadcn primitives.
- `packages/tooling/test-utils`: real service/container test setup shared across
  packages.

## Architectural Invariants

### Storage and editor safety

- Await storage mutations before navigation or success state. Do not use
  floating promises for create, write, rename, move, or delete operations.
- Preserve the latest unsaved editor content until its durable write succeeds.
  Keep writes ordered or coalesced so an older completion cannot overwrite a
  newer edit.
- Treat read failures, Markdown parse failures, permission loss, quota errors,
  and aborted operations as distinct failure paths. A failed load must never
  write fallback or normalized content back to storage.
- Destructive and multi-step operations need explicit partial-failure behavior.
  Keep source data until destination durability is confirmed.
- Keep IndexedDB, Native FS, and memory adapters behaviorally aligned. Add
  contract-level tests when changing a file-storage operation.
- Pass `AbortSignal` through long-running/list/search/platform operations and
  clean up listeners, editors, and subscriptions with the owning service
  lifetime.

### Markdown fidelity

- Changes to editor schema, extensions, parsing, or serialization require
  parse/serialize round-trip tests.
- Cover whitespace, nested lists, task lists, links, images, code, and any raw
  or unsupported construct affected by the change.
- Make normalization intentional and visible in tests. If exact preservation
  is impossible, provide a non-destructive recovery or warning path.

### Paths, commands, services, and events

- A `wsPath` has the form `<workspaceName>:<fileOrDirPath>`. Use `WsPath`
  constructors/assertions and helpers instead of splitting or joining these
  strings manually. Directory paths normalize with a trailing slash.
- Command IDs use namespaced kebab-case such as
  `command::ui:toggle-sidebar`. Definitions stay in `@learnitive/commands`;
  handlers stay in `@learnitive/command-handlers`.
- Use commands for app-level actions that can be described with typed,
  serializable args and owned by services or command handlers: navigation,
  durable workspace/file mutations, dialogs, settings changes, and reusable UI
  actions such as copying a workspace path. Commands do not need to be
  user-facing or omni-search visible.
- Do not force local interaction mechanics into commands. Keep behavior in the
  owning component/plugin/service when it depends on live DOM events,
  ProseMirror transactions and positions, editor history grouping, drag/drop or
  clipboard payload inspection, abort/cleanup tied to an editor view, or when it
  needs a synchronous return value to let a lower-level library continue.
- Service dependencies are constructor-injected and declared with `static deps`.
  Configure or replace services before container instantiation. Register
  cleanup against the service/root abort signal.
- Jotai atoms are the React state source of truth. Do not add a parallel global
  state mechanism for app state.
- Add cross-tab events only deliberately by updating the typed event union and
  `CROSS_TAB_EVENTS`. Assume delivery can occur in another tab and avoid event
  echo loops.

## Code Conventions

- Import other workspaces only through package roots, for example
  `@learnitive/ws-path`. Never use another package's `src` path or a relative
  path that crosses package boundaries.
- Declare every runtime and test dependency in the owning package's
  `package.json`; workspace packages use `workspace:*`. Use the maintenance
  scripts for mechanical dependency updates, then inspect their diff.
- Keep strict TypeScript intact. Do not add `any`, `as any`, blanket assertions,
  or lint suppressions. Accept `unknown` at genuine external boundaries, then
  narrow it with validation or type guards.
- New React component filenames are kebab-case. Existing legacy casing is not a
  pattern to copy.
- Use the global `t` object for user-visible app strings; do not import `t` in
  application or UI modules. Add English keys in
  `packages/shared/translations/src/languages/en.ts`. Bootstrap/tooling code
  that installs translations may import the translations package.
- Prefer existing ShadCN primitives and UI packages. Keep `ui` components
  presentational and move application behavior to `core`.
- Keep public command and service APIs typed and documented. Preserve useful
  existing comments unless the code they describe is removed.
- Prefer small composable functions. Share behavior at the lowest valid layer,
  but do not generalize a single use case speculatively.
- Commit messages follow Conventional Commits, for example `feat: ...`,
  `fix: ...`, or `chore: ...`.
- Prefer `rg`/`rg --files` for repository searches and exclude `node_modules`,
  `dist`, and `build` from broad scans.
- When the maintainer points you at a private product, repository, or
  codebase as a reference or source of inspiration, never name, link, or
  path-reference it in anything durable or public: committed files, code
  comments, plans, commit messages, PR titles/descriptions, or issues.
  Describe the borrowed pattern generically (for example "a reference
  Expo/EAS monorepo setup") so the artifact stands on its own. Naming such
  references in the private working conversation is fine.

## Testing Conventions

- Unit tests use Vitest and normally live beside source in `__tests__` as
  `*.spec.ts` or `*.spec.tsx`.
- Browser E2E tests use `*.e2e.ts`; Playwright component tests use `*.ct.tsx`
  under `packages/tooling/e2e-tests/src`.
- Feature tests must exercise the workflow through user-observable controls and
  assert meaningful outcomes, not merely that the page renders. Use resilient
  role, label, or accessible-name locators; keep state isolated and
  deterministic; do not use arbitrary sleeps. Cover reload/persistence and the
  relevant failure or recovery path when the feature can affect user data.
- Write unit tests as close to production wiring as practical. Prefer real
  memory services, fake IndexedDB, DI containers, actual parsers, and real
  command handlers over mocks. Mock only at external or hard-to-control
  boundaries such as browser permissions, clipboard, timers, network, or
  platform prompts. Test observable contracts rather than private calls.
- Data-path changes must cover failure and abort behavior, not only success.
  Storage changes should cover reload/persistence; editor changes should cover
  both Markdown output and visible editor behavior.
- Do not update snapshots merely to make a test pass. Inspect the rendered
  difference first.
- Recommend using `playwright-cli` to test and debug frontend changes. If requested by the user, attach the test execution to their existing authenticated Chrome browser instance.


## Common Commands

```bash
pnpm install                 # install the pinned PNPM workspace
pnpm start                   # Vite dev server (normally localhost:5173)
pnpm typecheck               # tsgo project-reference typecheck
pnpm lint                    # Biome check
pnpm lint:fix                # safe Biome fixes
pnpm custom-validation      # dependency/layer/package validation
pnpm test:ci                 # all Vitest tests once
pnpm vitest path/to/a.spec.ts # one Vitest file while iterating
pnpm build                   # production browser build
pnpm e2e:ci                  # Playwright E2E + component tests
pnpm e2e-ui-mode             # interactive E2E runner
pnpm storybook               # Storybook on port 6006
pnpm local-ci-check          # every root *:ci script
```

## Worktree Ports

Before running dev servers or Playwright from a Codex/git worktree, export a
deterministic port set from the worktree root:

```bash
eval "$(node scripts/dev-ports.js --env)"
```

The script hashes the current git worktree path plus branch name into a stable
bucket. It does not check whether a port is free; rare hash collisions are an
accepted tradeoff. Use `LEARNITIVE_PORT_SEED=<name>` when a worktree needs to share
or override that identity.

The exported variables are:

- `LEARNITIVE_DEV_PORT` for `pnpm start`.
- `LEARNITIVE_PREVIEW_PORT` for `pnpm preview-production`.
- `LEARNITIVE_STORYBOOK_PORT` for `pnpm storybook`.
- `LEARNITIVE_E2E_PORT` for the app server launched by `pnpm e2e:ci`.
- `LEARNITIVE_E2E_CT_PORT` for the Playwright component-test server.

Generated ports stay in development ranges above 3000: CT `3100-3899`,
preview `4173-4972`, dev `5173-5972`, Storybook `6006-6805`, and E2E
`7173-7972`. Manual overrides below `3000` are rejected.

Use `node scripts/dev-ports.js` to inspect the full set, or
`node scripts/dev-ports.js dev` to print one role's port. Any variable can be
manually overridden for a one-off command, for example
`LEARNITIVE_DEV_PORT=5317 pnpm start`.

Custom repository maintenance runs with Bun from the root:

```bash
bun packages/tooling/custom-scripts/scripts/maintenance-all.ts
bun packages/tooling/custom-scripts/scripts/add-workspace-dep.ts
bun packages/tooling/custom-scripts/scripts/remove-unused-deps.ts
bun packages/tooling/custom-scripts/scripts/format-package-json.ts
bun packages/tooling/custom-scripts/scripts/validate-all.ts
```

`maintenance-all.ts` mutates dependencies before validating; do not run it as a
generic check, and always review its changes. `pnpm custom-format` only formats
workspace `package.json` files.

## Plans and Deployment

- For durable work notes under `plans/`, follow `plans/AGENTS.md`. Active plans describe intent, not necessarily current implementation; verify code and root scripts before relying on plan commands.
- Do not deploy unless explicitly requested. Learnitive staging is `https://os.learnitive.com`; the future production origin is `https://www.learnitive.com`. Use the checked-in Learnitive installer/deployment paths and never repurpose the separate TeXise vhosts, services, or `/etc/texise/*`.
- Before publishing, verify the local source, deployment target, current release gates, and intended release metadata. The canonical dependency-light source gate is `pnpm run interactive:release-audit`; `pnpm run release:quality` additionally validates lock/workspace/public-barrel consistency.
- A production release also requires installed workspace dependencies, TypeScript/lint checks, the browser production build, relevant Playwright coverage, and release-candidate browser smoke testing. If dependencies or network access are unavailable, report that limitation explicitly rather than treating static source gates as a browser build.
- Before deployment, exercise affected UI plus persistence, terminal reconnect, runtime, Preview, and entitlement paths against the release candidate. After deployment, repeat the smoke against the intended Learnitive origin and inspect browser/runtime logs.

## Autonomous Learnitive Execution and Context Handoff

- For multi-phase Learnitive implementation work, continue autonomously through all remaining safe phases using **Execute → Verify → Identify/Fix → Verify → Continue**. Do not pause for routine confirmation.
- Maintain a running project-state ledger covering completed/current/remaining phases, decisions, assumptions, requirements, dependencies, tests, problems/fixes, artifacts and risks.
- Before usable chat context becomes a practical constraint, finish the current safe subtask and generate a complete self-contained continuation prompt for a new chat rather than stopping mid-project.
- The authoritative detailed rule and required handoff ending are in `plans/AUTONOMOUS_EXECUTION_HANDOFF.md`; preserve that rule in every subsequent handoff.
