Stella is an open-source legal workspace and set of legal-data tooling.
International audience: do not assume English language or English typography conventions are universal. Highlight competing standards (date formats, quotation marks, citation styles, legal terminology) when relevant.
- Treat legal data, personal data, and repository secrets as sensitive.
- Keep project instructions, PRs, commits, and comments limited to public engineering context visible from the repository and diff.
- Do not publish private user, customer, infrastructure, incident, pricing, roadmap, or competitive context in generated instructions or GitHub artifacts.
Stella code is intended for use in environments with SOC 2 and ISO 27001 style controls. Treat security, auditability, least privilege, data minimization, and workspace isolation as baseline engineering requirements.
When making changes, prefer designs that preserve clear ownership boundaries, structured audit trails, encryption-aware data handling, and explicit access checks. Keep public PRs and comments focused on the implementation visible in the diff; do not describe private controls, internal security architecture, or certification details unless they are already public in the repository.
- When commenting on GitHub (PRs, issues), append
CC on behalf of username, whereusernameis the GitHub handle of the person who requested the comment. Keep the handle as plain text: never prefix it with@or link the account, because the attribution must not trigger a GitHub mention notification. - This repository (including PRs, commits, comments) is public. Never include marketing language, internal business context, pricing, competitive analysis, user identities, conversation specifics, or security architecture beyond what the diff shows. Write for the reviewing engineer.
- Never manually reformat code you did not semantically change (auto-formatter output
from
bun run formatis fine to include) - In prose, vary punctuation: prefer colons, semicolons, commas, and parentheses over em dashes. This does not apply to source code, command syntax, generated content, or exact identifiers.
- Omit needless words. Vigorous writing is concise: a sentence should contain no unnecessary words, a paragraph no unnecessary sentences, for the same reason that a drawing should have no unnecessary lines and a machine no unnecessary parts. Applies to comments, commits, PRs, and docs.
- Prefer explicit over implicit; when a backend endpoint accepts a discriminator
(e.g.,
?type=document|file), thread it through the full stack (URL params, component props) instead of hardcoding a default on the frontend - If TypeScript can make a class of bug structurally impossible (branded types, discriminated unions, exhaustive checks), prefer that over runtime validation or manual discipline
- Kill the bug class, not the instance: for a recurring or systemic defect, use the strongest applicable mechanism. Make invalid states unrepresentable first; cover remaining behavior with a property, invariant, idempotence, or fixed-point test over the input class. Enforce boundaries with a lint rule or CI check. Keep a minimal example regression only when the broader invariant cannot express the failure. When correctness depends on a helper being called at every call site, enforce it with a custom lint rule, not developer discipline. Do not over-apply this to genuine heuristics (a debounce timer is not a bug class).
- Avoid boolean fields for states that may grow. Use a named discriminator or
domain type for values that answer "which kind/status/mode/type?" rather than
a permanent yes/no question; a two-value union, enum, or equivalent domain type
now is usually cheaper than migrating an
isXflag later. - Conventional Commits:
feat:,chore:,fix:,docs: - Rebase feature branches onto main (linear history)
- Enable
git rerere(git config --global rerere.enabled true, plusrerere.autoupdate trueto auto-stage what it resolves) so conflict resolutions are recorded and auto-replayed across repeated or long rebases - Fail fast: validate at boundaries, return/throw early
- Minimize brace nesting: invert conditions, early returns
- Use named constants, not string literals for domain values
- No direct
document.cookieassignment - Avoid spread in loop accumulators (use
.push()) - If you encounter a pre-existing bug or lint error, fix it. Preserve focus through isolation, not omission: use a separate commit when the fix is small and shares the same validation surface; use a separate focused PR when it expands the subsystem, risk, or review burden. Never leave a confirmed defect merely to keep a diff narrow.
- Orchestrate across model tiers when your harness supports subagents and model selection: delegate well-scoped, mechanical, or independently verifiable subtasks (edits, searches, refactors, test runs) to a subagent on the cheapest model that does them correctly; keep planning, cross-cutting design, security-sensitive work, and final review on the primary model. If your tooling has no subagents or model selection, ignore this.
- No hidden complexity; code is the docs. Every operation must work for humans, scripts, and AI agents alike.
- No lock-in: standard formats, self-hosting is first-class.
- AI is a tool, not a persona. No anthropomorphizing.
- Performance is non-negotiable. Batch operations, minimize round-trips, lazy-load aggressively.
- Vertical slices over horizontal layers. Features are independent end-to-end slices (own routes, components, handlers). New capabilities land in their own slice; existing code stays untouched.
- No enums: use
as constobjects or union types - Model mutually exclusive internal states as discriminated unions with a stable
type,status, or domain-specific discriminator. Avoid boolean flag sets plus optional payload fields when only some combinations are valid. - Construct a discriminated-union branch transition explicitly: list the target
branch's fields rather than spreading the previous object and overriding the
discriminator, so stale fields from the old branch cannot leak through the spread.
Read a union with a
switchplus aneverexhaustiveness check over anif/elsechain. - When the linter blocks an
ascast, restructure to narrow properly (type guards,inchecks, records instead of arrays). If truly unavoidable, ask before adding and include a// SAFETY:comment explaining why the cast is sound. - When a type mismatch appears, trace it to the source (e.g., the handler or query that produces the wrong type) rather than casting at the consumer. Check git to verify you did not introduce the mismatch yourself before blaming the framework.
- Never annotate or cast a value the compiler already infers, and never pass explicit type arguments to inference-driven APIs. Every redundant annotation or generic can mask real errors and break the inference chain; let inference flow and narrow at the boundary instead.
- Validate object literals against a large union type (route, link, query options) with
as const satisfies T, not a: Tannotation.satisfieschecks the value without widening it or paying the annotation's instantiation cost. - Use
.at(0)when the element may not exist (signals possible absence). Use[0]only when existence is already established (length check, or a// SAFETY:comment). - Skip barrel files (
index.ts); import from explicit module paths. - Prefer arrow functions over function expressions
- Destructure in the parameter when the intermediate variable is not reused
(e.g.,
{ body: { file, name } }notbodythenconst { file, name } = body) - Prefer discriminated union narrowing (
obj.type === "x") over"key" in objchecks. Useinonly when the type is not a discriminated union and there is no discriminator to check. - For function arguments, including helpers: use normal typed parameters for one
argument, and also for two arguments when their types are different enough to stay
readable. Use a named
SomethingOptions,SomethingArgs, orSomethingParamsobject for 3+ arguments, or when two same-type or otherwise interchangeable positional arguments would be easy to mix up. - Reuse utility types from libraries instead of hand-rolling equivalents. Check the dependencies already in use before defining a custom helper type.
- Keep helper-local types close to the helper they describe: put
SomethingOptions,SomethingResult, and similar aliases immediately above the function, not in a file-level type dump far away from the implementation. - If a return type is noisy enough to hurt readability, hoist it into a nearby alias
such as
SomethingResultand use it in the signature (e.g.,SomethingResultorPromise<SomethingResult>). If the return type is simple, keep it inline. - Watch type-instantiation cost in hot generic paths such as schema builders, route trees, and query-option graphs. Prefer narrowing over annotation, and keep large unused types out of inferred return positions.
- No module-level side effects in shared modules. If a module exports both a side-effecting singleton (DB connection, auth client, pool) and reusable utilities, split them: put utilities in a separate file so consumers can import them without triggering initialization. The side-effecting module re-exports for convenience.
- Never import test-only types in prod code. If a prod generic needs to accept
both prod and test instances, use a structural constraint (
{ transaction: ... }) instead of importing a type from a test file. - Defer eager initialization with lazy singletons. When a module-level call
(
betterAuth(),drizzle()) depends on another module's export, wrap it in agetX()getter so it runs at first use, not at import time. This prevents TDZ errors from non-deterministic module evaluation order.
- Use
better-resultfor typed error handling. Do not use try-catch for control flow; wrap failable operations withResultinstead. Try-catch is only acceptable at boundary layers (top-level request handlers, framework hooks). - Split error semantics deliberately: use
panic(...)for impossible internal invariants and programmer misuse,TaggedErrorsubclasses for expected business/config/runtime failures, and analytics/logging capture for telemetry-only paths that continue execution. - Prefer tagged errors (
APIError,TaggedErrorsubclasses) over barenew Error(). Tagged errors carry structured context (status, cause) for error handling and reporting. EveryTaggedErrormust include amessage: stringfield. - All errors must be surfaced to the user (toast) or propagated to the caller. Capture errors before throwing (PostHog). Never swallow errors silently.
- Do not leave ad hoc
console.error(...)in product code. Route telemetry-only failures through the shared analytics or logging helpers so observability stays structured.
Only test what can actually go wrong: bugs the type system, framework, or linter would
miss. Prefer invariants over examples when the input space is large. Full conventions
in /conventions-testing.
oxlint (ultracite preset) + oxfmt. To suppress a rule:
// eslint-disable-next-line rule-name
folio is a Bun-first TypeScript monorepo for browser-based Word-document (.docx)
editing. Its published packages have explicit ownership boundaries:
@stll/docx-core(packages/docx-core) owns the typed OOXML document model, validation, serialization, legal-source compiler, and portable DOCX projection kernel. The kernel's Rust source lives incrates/docx-kernel; the package exposes its browser binding through@stll/docx-core/projection.@stll/folio-core(packages/core) owns DOCX parsing, ProseMirror integration, framework-neutral editor behavior, and page layout.@stll/folio-reactand@stll/folio-vue(packages/react,packages/vue) are thin framework adapters over folio-core.@stll/folio-nuxt(packages/nuxt) provides the SSR-safe Nuxt integration.@stll/folio-agents(packages/agents) provides agent tooling over the public editor contracts.packages/playgroundandpackages/playground-vueare private test applications; they are not published.
bun installbun run buildbun run typecheckbun run testbun run test:propertybun run lintbun run format:checkbun run validate-distbun run test:interactionsbun run test:e2e:vuebun run test:e2e:paritybun run test:differentialbun run orient -- path/to/file.tsorbun run orient -- --diff main
- At the start of an unfamiliar change or regression, run
bun run orienton the suspected files or current diff. Use its seam, source relationships, focused tests, required checks, and changeset report as the initial investigation route; verify behavior in code before acting on any advisory result. - Preserve upstream attribution. folio is a fork of the Eigenpal docx-editor (see
NOTICE.md).NOTICE.md,LICENSE, and theeigenpal/docx-editorattribution comments must stay verbatim; never scrub them. - Keep React and Vue public contracts in parity. Run
bun run check:parity-contractandbun run check:export-paritywhen an adapter contract changes. - Return minimal data from public APIs; do not export types with no consumer.
- Keep DOCX projection semantics in
crates/docx-kernel. TypeScript bindings may initialize WebAssembly, preserve its versioned result, and translate boundary errors; they must not contain a fallback OOXML parser or duplicate Rust logic. - Keep the portable DOCX kernel single-threaded and browser-native: no WASI, workers,
shared memory,
SharedArrayBuffer, or cross-origin-isolation requirement. Runbun run check:docx-kernelafter Rust changes; the gate checks deterministic generation, runtime constraints, and transfer-size budgets. - Resolve OOXML elements by namespace URI plus local name, never by a hard-coded prefix. Support Strict and Transitional namespace profiles explicitly, bound ZIP and XML resource use, and preserve package paragraph identifiers as facts rather than treating them as durable application or host-navigation identities.
- Add a changeset for any published-package
srcchange. Select every affected package, choose the appropriate bump, and add a one-line summary. Usebunx changeset --emptyonly when the source change intentionally needs no release. The private playground packages need no changeset. - Never delete or regenerate
bun.lockto apply package version bumps. Runbun scripts/check-lockfile-workspace-versions.ts --write, thenbun install --frozen-lockfile. The synchronizer owns cached workspace self-versions; dependency-graph changes belong in an explicit install.
Toolchain and standard commands live under ## Repository Specifics → ### Commands;
this section records only non-obvious cloud-VM caveats.
- Toolchain lives in the user profile, not the base image. Bun
1.3.14is at~/.bun/bin; Node is provided by nvm (defaultv22.22.2). A login shell sources~/.bashrcand activates both. A plain non-loginbash -cfalls back to/exec-daemon/node(Node 22.14). - Build and dist validation need Node ≥ 22.18. Use the nvm default Node before running commands that load tsdown configuration.
- Playwright Chromium is optional locally and required for interaction, visual, and
adapter e2e suites.
python-docxis optional for differential tests, which skip when it is unavailable. .ai/sharedis a submodule. Initialize it before runningbun run sync-ai; do not fetch a floating shared revision during normal CI.