Keep the handbook useful
Documentation ships with the feature. If a change affects what a person can do, what an integration sends or receives, or how an operator runs Flare, update the corresponding guides in the same commit. This applies to coding agents and human contributors alike. The repository’s AGENTS.md makes this part of the definition of done.
Why the same repository?
The implementation, examples, screenshots, and documentation can be reviewed and released together. A separate docs repository would make it easier for a feature to ship without its guide. The docs are still an independent static site: Flare does not need VitePress, Vue, or a documentation server at runtime.
Markdown and theme sources live in docs/site/. Its dependencies and lockfile are separate. Build output and prepared media are ignored by Git and excluded from Flare’s Docker build. The asset script reuses the existing docs/images/ and .github/assets/ libraries, converts referenced images to WebP, and copies only the needed recordings. Do not commit duplicates of generated media.
Run locally
Use Node.js 24 (the CI version), then run from the repository root:
npm ci --prefix docs/site
npm run dev --prefix docs/siteThe development server prints its address. Content, theme, and local-search changes update as you work. Restart dev after adding a new screenshot reference so the asset preparation step runs again.
To build and inspect the actual static output:
npm run check:coverage --prefix docs/site
npm run build --prefix docs/site
npm run preview --prefix docs/siteThe output is docs/site/.vitepress/dist/. build checks links, anchor targets, assets, and OpenAPI references after rendering. A missing guide or image should fail the build instead of becoming a broken page for readers.
This preview describes the checked-out source, including unreleased changes. The public site uses the separate release build below: the homepage follows stable, and readers must explicitly open the rolling preview for development prerelease instructions.
Build the release site
From a checkout with the complete Git history and release tags:
git fetch origin main 'refs/tags/v*:refs/tags/v*'
npm ci --prefix docs/site
DOCS_BASE=/Flare/ npm run build:releases --prefix docs/site
DOCS_BASE=/Flare/ npm run test:releases --prefix docs/siteThe release builder reads GitHub's published release metadata and resolves each stable release tag to its source commit. The latest stable release supplies the root handbook; every published stable release also has an archive at /versions/TAG/ below DOCS_BASE. A release without the handbook uses the README and any earlier guides from its own tag. The builder does not fill missing historical guides with current instructions.
The same build adds /rolling/ from the immutable commit recorded in the published rolling release's flare-commit-sha marker. The application uses this marker for rolling update checks too. The mutable local rolling tag and the publishing checkout are not substitutes: a local tag can be stale, and main can be ahead of published Docker images. The fetch command retrieves version tags without attempting to overwrite an existing local rolling tag. The builder fetches the published rolling commit if it is missing locally. Drafts and other prereleases are excluded; rolling is separate from the stable archive list and comparisons.
The output is docs/site/.vitepress/releases/. build:releases validates the assembled site; test:releases serves that output and checks stable content, archive navigation, dated releases, comparisons, and explicit rolling access in a browser. Install Chromium as described in Browser checks before the first test run. Keep this generated output, intermediate checkouts, and downloaded release metadata out of Git.
Run one release build at a time. The builder copies the verified site into a sibling staging directory before replacing the previous output, then keeps a backup until the replacement succeeds. A failed copy leaves the old site intact; a failed replacement rolls back, retaining the backup if rollback cannot finish. The output path can briefly be absent between the two directory renames. If a build is interrupted there, the next build:releases run restores the backup before starting its build; after a completed replacement, it removes any leftover backup. The staging and backup directories are generated files and stay out of Git.
The build needs GitHub API access. In CI, GH_TOKEN receives the read-only repository token. Locally, GH_TOKEN or GITHUB_TOKEN can supply authentication, or the builder uses your authenticated GitHub CLI. Public release metadata also works without authentication within GitHub's unauthenticated rate limit. Keep tokens in your environment; never place one in a command committed to this repository. If fetching metadata or a source commit fails, resolve the reported access or Git error and rebuild. Do not substitute main for a missing stable release or rolling commit, or reuse a partial output directory as a deployment.
What a complete update includes
| If you change… | Update and verify… |
|---|---|
| A user workflow | Steps, labels, expected result, privacy/limits, troubleshooting, and current screenshots |
| An admin control | Who can change it, default, effect on existing users, related user-facing behavior, and recovery |
| Hosting or persistence | Configuration reference, deployment commands, backup/restore, upgrade and migration steps |
| An API route | Endpoint inventory, actual authentication, request and response schema, statuses, units, examples, OpenAPI where token-supported |
| A webhook | Event schema, signature verification, retry behavior, operating limits, and working receiver example |
| A capability or navigation | Feature explorer, sidebar, cross-links, tour, and examples that mention it |
Trace claims to the source. A route existing under /api/ does not mean a named token can call it. A “private” checkbox does not mean administrators cannot read the data. An account export is not a server backup. Explain these boundaries directly.
Do not add filler to satisfy a gate. For an internal change with no user-visible effect, record why the existing guidance remains correct in the PR and review the relevant pages. Coverage checks are structural aids; they cannot establish that the prose is complete or true.
Screenshots and recordings
Use a local instance and demonstration data. Never photograph a real token, secret, private file, or customer account. Capture the real interface; do not reconstruct it in an image generator. Inspect the image before adding it, especially after feature removals or renamed controls.
Use the globally registered component:
<Screenshot
src="/screenshots/handbook/upload-profiles.webp"
alt="Flare’s upload profile editor showing saved sharing defaults"
caption="Choose which options a profile overrides; leave others inherited."
/>/screenshots/ maps to source images under docs/images/; /evidence/ maps to .github/assets/. Use a literal source path so asset preparation can find it. PNG and JPG source paths automatically become generated WebP URLs. New source images should normally already be compact WebP files. Keep one canonical source and let the build prepare it.
Recordings need a useful written transcript, manual playback, and preload="none". Respect reduced-motion preferences. Clearly distinguish recorded real app behavior from the local educational simulations in the interactive labs. Do not add an unlabeled fake server or a demo that secretly calls a reader’s instance.
Project credits and the author button
The handbook footer credits Flare’s creator, FlintSH, and links to fl1nt.dev. Keep this credit on the homepage and every guide, alongside the MIT license and xNefas’s icon credit.
ProjectCredit.vue uses the official 88 × 31 button from https://fl1nt.dev/images/mybutton.gif. This follows the website’s Hotlink my Button! → Copy Code snippet. Keep its original dimensions, colors, and pixel art; do not stretch, recolor, or redraw it. The image is hotlinked, so no duplicate brand asset is added to the repository. The visible author name and website remain a working link if the image cannot load.
Browser checks
From docs/site/:
npx playwright install chromium
npm testTests use the production build and cover local search, feature filtering, screenshot keyboard dismissal, upload-option precedence, additive role permissions and token scope intersection, safe API code generation, version/commit provenance, desktop/mobile overflow, failed asset requests, and automated WCAG checks in light and dark themes. The Git policy fixture also proves that a later documentation commit cannot cover an earlier undocumented feature commit. Linux machines may need Playwright's system dependencies; CI uses npx playwright install --with-deps chromium.
The separate npm run test:releases suite verifies the assembled stable site, release archives, and rolling preview after npm run build:releases. Run both suites when changing version navigation, comparisons, or publishing. Source-preview tests cannot establish that deployment selected the correct stable and rolling revisions.
When changing links or theme components, also test a subpath build:
DOCS_BASE=/flare/ npm run build
DOCS_BASE=/flare/ npm testDOCS_BASE must begin and end with /. Afterward rebuild without that variable if you want a root deployment. For Vue components, use withBase() for local links and assets. Regular Markdown links are handled by VitePress.
Dependency maintenance
Keep Flare's pnpm-lock.yaml and the handbook's package-lock.json separate. Use Node.js 24 and the pnpm version in the root package.json; install with pnpm install --frozen-lockfile and npm ci --prefix docs/site. Check both dependency trees with pnpm audit and npm audit --prefix docs/site before a release. A clean audit describes the advisories known at that time, not a guarantee against future findings.
The 2.1 dependency refresh keeps the existing application framework versions compatible. The root overrides update Prisma's configuration merger and Meticulous's browser installer to address vulnerable transitive packages. The browser installer includes its proxy and ZIP extraction dependencies so local testing still works without requiring a system unzip command. The handbook separately overrides Vite to its patched 6.4 line while retaining stable VitePress. Recheck these overrides against upstream releases before removing them, and run application tests, database migrations, production builds, and local browser checks after changes.
Database permission and avatar regression tests
The role/cleanup and avatar race suites require two separate disposable PostgreSQL databases. Create them on a loopback server with names beginning flare_roles_test_ and flare_avatar_test_. Use a local test database role allowed to apply migrations and manage fixture tables. Both suites clear fixture tables in their dedicated databases. Do not use an application or browser-demo database for these tests.
From the repository root, set connection URLs appropriate to that disposable server. These examples assume the local test role can connect without a password; supply your test server's authentication and port when needed:
export FLARE_ROLES_DATABASE_URL='postgresql://flare_test@127.0.0.1:5432/flare_roles_test_local?schema=public'
export FLARE_AVATAR_DATABASE_URL='postgresql://flare_test@127.0.0.1:5432/flare_avatar_test_local?schema=public'
DATABASE_URL="$FLARE_ROLES_DATABASE_URL" pnpm exec prisma migrate deploy
DATABASE_URL="$FLARE_AVATAR_DATABASE_URL" pnpm exec prisma migrate deploy
pnpm exec vitest run \
__tests__/permissions/database.test.ts \
__tests__/storage/avatar-database.test.tsApply migrations to both databases before running the suites. Missing either environment variable skips that suite; check the test output rather than treating a skipped run as coverage. CI supplies both URLs. The database checks exercise role authority, recovery safeguards, bulk account cleanup, original storage targets, upload/deletion races, and durable avatar intents. They use real PostgreSQL and local files, with controlled S3 SDK responses; they do not substitute for testing a real S3-compatible provider.
The browser/API role recipes cover the rendered controls and real session behavior separately.
Continuous library browser checks and demos
The library guide and recorded walkthrough use a disposable account with 12,000 files dated from July 2018 to October 2026. The uploaded landscape illustrations and dates are demonstration fixtures. Captures render the actual application and use its real database and file requests.
Create a disposable local PostgreSQL database named exactly flare_timeline_test_local. The seed accepts only localhost or 127.0.0.1 with the public schema and refuses a database containing accounts other than its two fixtures. It resets those fixture accounts and writes their local file objects beneath uploads/timeline-demo/; do not point it at an existing application installation. Install the root dependencies and the separate handbook dependencies first; the scripts use the handbook's Playwright and Sharp packages. Install Chromium as described in Browser checks before the first run.
From the repository root, use your disposable database role and authentication:
export DATABASE_URL='postgresql://flare_test@127.0.0.1:5432/flare_timeline_test_local'
export NEXTAUTH_URL='http://localhost:3064'
export NEXTAUTH_SECRET='public-disposable-timeline-demo-secret-2026-only'
export METICULOUS_RECORDING_ENABLED=false
export NEXT_PUBLIC_METICULOUS_RECORDING_TOKEN=''
pnpm exec prisma migrate deploy
node scripts/migrate-config.js
node scripts/timeline/seed.cjs
pnpm exec next dev --hostname 127.0.0.1 --port 3064In another terminal, run the real browser checks:
export FLARE_TIMELINE_TEST_ORIGIN='http://localhost:3064'
node scripts/timeline/verify-ui.cjsThe demonstration account is timeline-demo-alex@example.test with the deliberately public password Timeline-demo-only-2026!. The second account supplies an ownership-isolation check. The checks use UTC so dates are reproducible; the shipped library uses each viewer's browser time zone. This is local browser coverage, not a hosted Meticulous run or a benchmark of production storage.
The browser checks cover direct date jumps, bounded mounted cards and file requests, selection and archive inputs across scrolling, fresh bulk-tag membership after a real external API change, search and folder totals, date grouping, alternate sorts, image navigation, old page links, and position preservation when using browser Back or resizing between desktop and mobile. One retry check simulates a network failure by aborting a request; the retry fetches real files from the local server. The recordings omit that injected failure and the separate archive-input and current-tag membership regressions. Set FLARE_TIMELINE_RESULTS to a temporary JSON path to save the measured counts and check results alongside the console output.
At desktop and mobile widths, the checks also open Create archive after selected cards have scrolled out of the rendered window, verify the selected filenames and private defaults, and cancel without creating an archive. Closing that dialog clears the selection. With screenshot output enabled, the desktop check captures library-archive.webp with the selected-file list expanded. These unrecorded checks complement the separate archive suite’s real creation and extraction operations below.
Separate unrecorded checks create and revoke disposable named tokens, verify selected-ID listing and timeline counts stay within the token owner's account, reject an upload-only token on those reads, and run examples/integrations.mjs files against the real local server. They verify request-level audit attribution to the owner and token ID, including denied reads, without retaining token secrets in audit details or captures.
The retained-selection tagging regression has a separate check against the same disposable server:
export FLARE_TIMELINE_TEST_ORIGIN='http://localhost:3064'
node scripts/timeline/verify-tag-refresh.cjsIt selects a tagged file, removes the tag through another authenticated client, refreshes, and verifies that Edit tags reads current membership and sends an addition when the unchecked tag is clicked. It also checks selected files that have scrolled off screen, mixed membership, recovery from a failed membership request, and cancellation when a loading dialog closes. The failed and delayed requests are simulations; successful reads and tag changes use the real local application. The check restores its fixture tag assignments afterward.
Set FLARE_TIMELINE_SCREENSHOTS to a temporary directory to capture library-tags.webp during the successful membership read, before the simulated request failure. Inspect it before replacing docs/images/timeline/library-tags.webp. This separate tagging image supplements the seven library screenshots captured by verify-ui.cjs.
Account changes in an open library
With the same disposable timeline database and server running, check an account change while the original library tab stays open:
export FLARE_TIMELINE_TEST_ORIGIN='http://localhost:3064'
node scripts/timeline/verify-account-switch.cjsKeep DATABASE_URL set to the same disposable database. The check accepts only the two seed accounts, adds 96 Jamie demonstration file records using existing fixture image bytes, and removes those added records afterward. It retains Alex's selection and an open Create archive dialog, signs in as Jamie through a real form in a second tab, and reloads only that second tab to trigger normal session synchronization. The original document stays open. It verifies that the old files and dialog disappear, Jamie's library loads, and date navigation cannot bring Alex's cards back. It also checks a replacement session for the same account and sign-out from the other tab.
Separate scenarios simulate delayed network delivery by holding completed, real timeline or file-list responses until after the account changes. They check that an old response cannot repopulate the new library; they do not fabricate a successful API response. These checks are separate from the ordinary scrolling recordings.
Set FLARE_TIMELINE_SCREENSHOTS to a temporary directory to capture library-account-switch.webp after the new account loads and passes date-navigation checks. Inspect it before replacing docs/images/timeline/library-account-switch.webp; the image should contain only the current demonstration account's files, with no old selection or dialog.
Set FLARE_TIMELINE_SCHEMA_EVIDENCE to a temporary JSON path to capture actual dated and undated timeline responses for two known fixture IDs. scripts/checks.test.mjs checks the published OpenAPI boundary types against the captured examples in scripts/fixtures/timeline-responses.json, including JSON null for non-date sorts. This is a focused contract regression, not validation of every OpenAPI operation.
Timeline API and capture output
The timeline API regression suite needs a separate disposable database, because it clears users and events between cases. For example:
export FLARE_TIMELINE_DATABASE_URL='postgresql://flare_test@127.0.0.1:5432/flare_timeline_test_api'
DATABASE_URL="$FLARE_TIMELINE_DATABASE_URL" pnpm exec prisma migrate deploy
pnpm exec vitest run __tests__/files/timeline-database.test.tsCreate that database first and keep it separate from flare_timeline_test_local. The API suite accepts only PostgreSQL on localhost or 127.0.0.1, with the exact database name flare_timeline_test_api or flare_timeline_test_ci. Omit the query string or use a single schema=public; other query parameters and URL fragments are rejected. Omitting FLARE_TIMELINE_DATABASE_URL skips the database suite; a skipped run is not database coverage. The code-quality CI workflow supplies and migrates its separate flare_timeline_test_ci database before running the suite.
To refresh the screenshots and silent recordings, set FLARE_TIMELINE_SCREENSHOTS to a temporary screenshot directory and FLARE_TIMELINE_VIDEOS to a separate temporary recording directory before running the browser script. Recording requires ffmpeg on your PATH with its libx264 H.264 encoder; ordinary browser checks and screenshots do not require it. The script converts the browser recordings to MP4 for playback in the handbook and PR links. Inspect every capture before replacing the nine canonical WebP sources in docs/images/timeline/ and timeline-scroll.mp4 / timeline-mobile.mp4 in .github/assets/timeline/. The main suite captures seven screenshots; the tag-refresh and account-switch checks each capture one more. Keep one source for each asset; the handbook build prepares its own copies. Update the written transcripts in demos if the recorded actions change. The mobile recording uses Chromium at a narrow viewport, not a physical phone. Keep temporary output, failed recordings, and fixture uploads out of Git.
Run these tools from a source checkout. The timeline fixture/capture scripts, their evidence directories, and the handbook tooling are excluded from Flare's Docker build context and application image.
Archive browser checks and demos
The archive workspace uses real uploads, sessions, storage, and database publication. Its local browser script creates ZIP, TAR.GZ, and GZIP fixtures, exercises owner-library browsing and extraction, packages selected files, and checks both private defaults and an explicitly selected public upload profile. It also tests anonymous share-page browsing, file-password protection, and individual entry downloads without offering extraction to recipients.
Use a disposable local PostgreSQL database named exactly archive_demo. scripts/archives/seed.cjs accepts only localhost or 127.0.0.1 and either no query parameters or a single schema=public. It resets the two public fixture accounts, uses local storage, and disables OCR and OIDC in that disposable instance. Do not point it at an existing installation. Create the database with your local PostgreSQL tools, then run from the repository root:
export DATABASE_URL='postgresql://flare_test@127.0.0.1:5432/archive_demo'
export NEXTAUTH_URL='http://localhost:3062'
export NEXTAUTH_SECRET="$(node -e "process.stdout.write(require('node:crypto').randomBytes(32).toString('hex'))")"
export METICULOUS_RECORDING_ENABLED=false
export NEXT_PUBLIC_METICULOUS_RECORDING_TOKEN=''
pnpm exec prisma migrate deploy
pnpm exec next dev --hostname 127.0.0.1 --port 3062Match the role, port, and database authentication to your disposable PostgreSQL server. In another terminal with the same DATABASE_URL, open http://localhost:3062/auth/login once to initialize configuration, then run:
node scripts/archives/seed.cjs
node scripts/archives/verify-ui.cjsThe public fixtures are archive-demo-alex@example.test and archive-demo-jamie@example.test, both with password Archive-demo-only-2026!. Reseed before repeating the browser suite. It verifies nested extraction, empty directories, exact downloaded content, unchanged originals, default private output, explicit profile visibility/tags/expiration, TAR.GZ creation, and ownership/session/origin failures. Shared reads separately check anonymous access, protected/private boundaries, body-only passwords on the archive routes, and exact entry downloads. It checks library dialogs and shared browsing at 390px wide and displays a real malformed-archive error. No remote storage provider is involved.
Set FLARE_ARCHIVE_SCREENSHOTS and FLARE_ARCHIVE_VIDEOS to separate temporary output directories to capture evidence. The primary recordings are archive-browse-extract.webm, archive-create.webm, and archive-share-browse.webm; inspect generated screenshots and recordings before replacing sources in docs/images/archives/ and .github/assets/archives/. The shared recording uses an anonymous, unprotected public archive. Protected-file checks produce separate stills; do not capture password values or credential-bearing URLs. Preserve written transcripts in Demos, and keep failed or incidental recordings out of Git. FLARE_ARCHIVE_TEST_ORIGIN can override the default http://localhost:3062, but must remain a disposable HTTP localhost origin matching the running app's NEXTAUTH_URL.
To repeat only the shared-page checks, reseed first, then use the optional mode. It still uploads its own fresh fixtures:
node scripts/archives/seed.cjs
FLARE_ARCHIVE_SHARE_ONLY=true node scripts/archives/verify-ui.cjsThe separate archive database suite requires its own disposable database; do not reuse the browser fixture database:
export FLARE_ARCHIVE_DATABASE_URL='postgresql://flare_test@127.0.0.1:5432/flare_archive_test_local'
DATABASE_URL="$FLARE_ARCHIVE_DATABASE_URL" pnpm exec prisma migrate deploy
pnpm exec vitest run __tests__/archives/database.test.tsThe suite accepts only local PostgreSQL databases named exactly flare_archive_test_local or flare_archive_test_ci, with the public schema. Without FLARE_ARCHIVE_DATABASE_URL it is skipped. It tests atomic publication and rollback, source ownership, quota, upload profiles and stale revisions, storage provenance, and safe filenames against real PostgreSQL, an in-memory storage provider, and real temporary-file codec operations. Shared archive tests also check current visibility/password access and revalidation after staging. The browser demos separately exercise the actual local storage provider. These checks complement the codec and provider tests; they do not establish real S3 compatibility or a Meticulous zero-diff result. If Meticulous authentication or a suitable recorded session is unavailable, report that gap and use these local checks without triggering a hosted run.
Shared-request admission has separate handler and body-guard regression suites:
pnpm exec vitest run \
__tests__/archives/handler-availability.test.ts \
__tests__/archives/shared-body.test.tsThey exercise the route handlers and body guards with streamed request bodies and controlled session/database/archive-service responses, without requiring PostgreSQL. They check that unfinished or rejected shared requests cannot occupy archive-processing slots needed by owner operations, alongside the body deadline, pending-read limit, cancellation, and capacity reuse. These request-admission checks complement the real storage/browser evidence above. Admission ordering changes errors and resource allocation without changing rendered controls or successful workflow steps, so the existing screenshots and recordings remain representative.
Security browser checks and demos
The sign-in security guide includes real application captures for authenticator setup, both recovery methods, passkeys, and the optional passkey requirement. The passkey recordings use Chromium's virtual authenticator: the application and server perform real WebAuthn ceremonies, while the virtual device stands in for a physical authenticator. They do not show or test a native biometric prompt or a live external SSO provider.
Use a disposable local PostgreSQL database named exactly flare_auth_demo. The seed script accepts only localhost or 127.0.0.1 and rejects other database names, including flare_auth_demo_backup. Omit the schema URL parameter or set it once to public; other schemas, duplicate schema parameters, and all other URL query parameters are rejected. It resets its two public fixture accounts and security rate counters. Never use an existing application database. With the dependencies installed, create the database using your local PostgreSQL tools, then start the app from the repository root:
export DATABASE_URL='postgresql://flare_test@127.0.0.1:5432/flare_auth_demo'
export NEXTAUTH_URL='http://localhost:3061'
export NEXTAUTH_SECRET='public-disposable-security-demo-secret-2026-only'
export METICULOUS_RECORDING_ENABLED=false
export NEXT_PUBLIC_METICULOUS_RECORDING_TOKEN=''
pnpm exec prisma migrate deploy
pnpm exec next dev --hostname 127.0.0.1 --port 3061The database role, port, and authentication must match your disposable server. In another terminal with the same DATABASE_URL, initialize the app configuration by opening http://localhost:3061/auth/login, then run:
node scripts/security/seed.cjs
node scripts/security/verify-ui.cjsThe fixture emails are security-demo-alex@example.test and security-demo-jamie@example.test; both use the deliberately public password Security-demo-only-2026!. The browser checks use real requests to enroll an authenticator, require a second factor at login, consume and reject reused authenticator recovery codes, replace codes, disable 2FA, register and use a passkey, rename/remove it, reject assertion replay and removed credentials, reject bearer/origin misuse, and invalidate old sessions. The required-passkey flow additionally blocks the correct password, refuses last-key removal, signs in with dedicated emergency codes without a password, rejects their reuse and replaced sets, and restores password sign-in only after an explicit disable operation. The ordinary browser run uses a 390 × 844 viewport for recovery and replacement flows. Recordings keep a stable desktop viewport; separate mobile stills use the same 390px width with extra height so the complete controls remain visible. A fresh TOTP time step may require waiting up to 30 seconds. Run the seed again before repeating the suite.
To refresh visual evidence, set FLARE_SECURITY_SCREENSHOTS to an output directory and FLARE_SECURITY_VIDEOS to a separate temporary directory before running the browser script. It captures screenshots as WebP and three completed walkthroughs as two-factor-demo.webm, passkey-demo.webm, and passkey-required-demo.webm. Secrets are hidden by capture-only CSS installed before application scripts: QR codes, manual keys, both recovery-code sets, and sensitive inputs never appear in recorded frames. The capture CSS does not alter the shipped UI. Inspect every resulting image and recording before replacing the canonical sources in docs/images/security/ and .github/assets/security/. Include updated written transcripts in the guide and demos. Keep temporary browser output and failed recordings out of Git.
To check a recent sign-in expiring while password/email edits or an Add a passkey dialog remain open, run this separate regression after reseeding:
node scripts/security/seed.cjs
node scripts/security/verify-proof-expiry.cjsThis check uses the exact public demonstration secret above and an HTTP localhost origin. It performs real authenticator enrollment and recovery sign-in, then adjusts only the demonstration session's authentication timestamp to just beyond five minutes. Server responses are real; elapsed time is controlled. It exercises three open forms:
- Enter password changes, expire the recent recovery proof, submit, and verify the code prompt appears without a profile mutation. Complete the proof and verify the password change succeeds.
- Repeat for a plain email change, verifying the entered address remains and no update is sent before the restored proof fields are completed.
- Open Add a passkey and enter a name while recovery proof is fresh. Expire that proof while the dialog stays open. Verify the name remains, password/code controls appear, and no security mutation is sent. Complete the proof and the virtual authenticator's real WebAuthn registration, then verify the saved passkey retains its name.
Optional FLARE_SECURITY_SCREENSHOTS output includes proof-expired.webp with password values masked. The check changes Alex's email/password and registers a passkey; reseed before another suite or demo run.
For the separate authentication database regression suite, create and migrate a disposable local database named exactly flare_security_test_local (or flare_security_test_ci for CI), then run:
export FLARE_SECURITY_DATABASE_URL='postgresql://flare_test@127.0.0.1:5432/flare_security_test_local'
DATABASE_URL="$FLARE_SECURITY_DATABASE_URL" pnpm exec prisma migrate deploy
pnpm exec vitest run __tests__/auth/security-database.test.tsWithout that variable the database suite is skipped. The guard accepts only PostgreSQL URLs on localhost or 127.0.0.1, using one of the two exact database names above and either no schema parameter or a single schema=public. Other URL query parameters are rejected. CI supplies its dedicated database. These tests cover atomic redemption for both code sets, stale-session fences, enrollment, encrypted secret handling, required-passkey transitions and fallback guards, and recovery for local and SSO-only accounts. They complement the browser ceremonies; neither substitutes for testing actual platform authenticators or a live identity provider.
Sessions and audit browser checks and demos
The profile session guide, audit guide, and recorded walkthroughs use real application operations with disposable accounts. They do not rely on production accounts or fabricated audit rows.
Create a disposable local PostgreSQL database named exactly flare_audit_demo using your local PostgreSQL tools. The seed guard accepts only localhost or 127.0.0.1, with schema omitted or set once to public; suffix database names, other schemas, duplicate schema parameters, and other URL query parameters are rejected. Use isolated uploads too. Never point this recipe at an existing application database: the seed replaces its demonstration accounts, clears security rate counters, and disables automatic OCR and OIDC in the fixture configuration.
With application dependencies installed, run from the repository root:
export DATABASE_URL='postgresql://flare_test@127.0.0.1:5432/flare_audit_demo'
export NEXTAUTH_URL='http://localhost:3071'
export NEXTAUTH_SECRET='public-disposable-audit-demo-secret-2026-only'
pnpm exec prisma migrate deploy
pnpm dev --port 3071Adjust the database role, port, and authentication to match your disposable PostgreSQL server. Open http://localhost:3071/auth/login once so the app initializes its configuration. In a second terminal with the same DATABASE_URL, run:
npm ci --prefix docs/site
cd docs/site
npx playwright install chromium
cd ../..
node scripts/audit/seed.cjs
node scripts/audit/verify-ui.cjsThe deliberately public fixture password is Audit-demo-only-2026!. The accounts are Alex Morgan (audit-demo-alex@example.test, administrator), Jamie Rivera (audit-demo-jamie@example.test), and Casey Chen (audit-demo-casey@example.test). Use FLARE_AUDIT_TEST_ORIGIN to select another localhost application origin; its default is http://localhost:3071. The script uses the documentation Playwright installation and can use PW_CHROMIUM_EXECUTABLE_PATH for an installed Chromium binary. Install Chromium's required system libraries on a new Linux host.
Run the seed before repeating the browser checks and inspect the JSON results and exit status. The workflow performs real account/file/security operations and OCR of a generated receipt test image, verifies that recognized text is absent from audit details, leaves demonstration evidence for inspection, and should run only against its disposable server. Discard its database and upload directory after verification. API examples in sessions and audit contracts can be executed in an authenticated console on this same instance.
Capture screenshots and videos in separate runs, reseeding before each. Set only FLARE_AUDIT_SCREENSHOTS to a temporary directory for desktop/mobile stills; for the recording pass, leave that variable unset and set only FLARE_AUDIT_VIDEOS. Element screenshots can briefly resize the browser and spoil a simultaneous recording. The videos keep desktop dimensions; mobile coverage is recorded separately in the stills and browser assertions. Review every image and both recordings for credentials, private data, readable pauses, and the final sign-in screen. Only then replace canonical sources under docs/images/audit/ and .github/assets/audit/. Keep generated copies under docs/site/public/, failed recordings, and temporary browser output out of Git. Update the descriptive transcripts on the demos page when the recorded steps change. The documentation browser checks verify that both recordings load without autoplay and that the new guides fit desktop and mobile viewports.
To verify archives and audit logging together on that same disposable instance, reseed and run the separate integration check:
node scripts/audit/seed.cjs
node scripts/audit/verify-archives.cjsThis uses real requests to upload a source, create and browse a ZIP, download an entry, and extract it. It verifies per-file attribution, denied and failed requests, selected member paths, and exclusion of entry contents from audit metadata, then opens the real administrator log with Category set to archives. Set FLARE_AUDIT_SCREENSHOTS to a temporary directory to capture archive-events.webp; inspect it before replacing the canonical audit guide image. This check uses the same FLARE_AUDIT_TEST_ORIGIN and fixture accounts as the sessions/audit suite. It does not replace the broader archive codec, shared-access, and resource-limit checks above.
Same-tab account and session isolation
Use the same disposable flare_audit_demo database and fixture setup above. To run this check on port 3072, start the application with that matching authentication origin in place of the earlier server:
export NEXTAUTH_URL='http://localhost:3072'
pnpm dev --port 3072Open http://localhost:3072/auth/login once to initialize configuration if needed. In a second terminal with the same DATABASE_URL, reseed and run:
export FLARE_AUDIT_TEST_ORIGIN='http://localhost:3072'
node scripts/audit/seed.cjs
node scripts/audit/verify-account-switch.cjsThis check loads Alex’s profile activity, revokes that exact browser session from a separate authenticated client, and follows Files to the real sign-in form for Jamie in the same tab. It checks that Alex’s activity is absent and Jamie’s activity is freshly loaded. It then remotely revokes Jamie’s session, follows the unvisited Upload link, and signs in as Jamie again, checking the new This browser marker while preserving Jamie’s legitimate earlier login history. A browser-document marker verifies that these transitions retain the same page context instead of clearing it with a full reload.
Requests and sign-ins use the real disposable server. Reserved example IP addresses are deliberately supplied as proxy headers to distinguish fixture sessions; they are not measured device locations or evidence that arbitrary proxy headers are trustworthy. Set FLARE_AUDIT_SCREENSHOTS to a temporary directory to capture account-switch.webp, and inspect it before replacing docs/images/audit/account-switch.webp. The eleven canonical audit screenshots include this check and archive-events.webp; the main session/audit suite captures the other nine. The existing session-management recordings show normal review and revocation; this separate check covers account/session transitions without changing those walkthroughs.
To check responses that arrive after an account switch, run the separate controlled network-delay simulation on the same disposable server:
node scripts/audit/seed.cjs
node scripts/audit/verify-account-switch-delays.cjsIt holds an unchanged real response after Alex’s session revocation has completed, then signs in as Jamie through a second tab’s real form before releasing the response. A second case holds the confirmation-requirements read for Set up authenticator across the same account switch. The second tab reloads after sign-in to trigger normal session synchronization; the original document remains loaded. The checks use native session updates and verify that an earlier response cannot sign out Jamie or start a security change for Jamie. Unexpected follow-up mutations are counted and blocked, causing failure; the script does not fabricate successful operations. The original tab may remain signed out after the second-tab login; the check requires Alex’s panels to disappear and verifies Jamie’s live session separately. These timing simulations complement the primary same-tab check, which does not intercept backend responses. They produce no screenshots or recordings.
Audit database regression checks
Create and migrate a separate local PostgreSQL database named exactly flare_audit_test before running the database suite. The guard accepts only localhost or 127.0.0.1; supported URL options are schema=public and connection_limit. These tests erase users, roles, settings, and audit rows in that disposable database, so keep it separate from both the browser demo and an existing instance.
export FLARE_AUDIT_DATABASE_URL='postgresql://flare_test@127.0.0.1:5432/flare_audit_test?connection_limit=1'
DATABASE_URL="$FLARE_AUDIT_DATABASE_URL" pnpm exec prisma migrate deploy
pnpm exec vitest run __tests__/auditWithout FLARE_AUDIT_DATABASE_URL, database cases are skipped while core/API tests still run. The PostgreSQL cases verify commit/rollback handling, transaction connection use, safe settings and role snapshots, bulk filename retention beyond 1,000 files, tag metadata, email-failure outcomes, and avoiding recursive logging. The one-connection example also checks that transaction snapshots do not wait on a second connection. These checks complement the rendered browser flows and the session-security tests.
Local visual testing with Meticulous
Flare no longer runs Meticulous in GitHub Actions or uploads builds for hosted test runs. The CLI, repository skills under .agents/skills/, browser/backend recorders, and disposable test image remain available for local visual checks. Agents can use meticulous-simulate-and-diff to replay relevant sessions against a local app and inspect screenshots during development. Follow the repository's AGENTS.md policy when a skill includes a final hosted run: that step is disabled here.
Local replay runs Chromium locally, but still uses Meticulous services for authentication, execution configuration, and session/replay data; simulation results are uploaded to Meticulous. It is not an offline service or a guarantee of free access to every command. Use the capabilities available to your account without starting paid hosted work. Do not run meticulous ci, meticulous agent upload-build, or meticulous agent trigger-test-run as part of routine validation. If service access or a usable session is unavailable, report that limitation and use local browser checks instead.
Start a disposable app
Run these commands from the repository root with Docker available:
docker build --build-arg METICULOUS_BUILD=true -t flare-meticulous-app .
docker build -f Dockerfile.meticulous -t flare-meticulous .
docker run --rm -p 127.0.0.1:3000:3000 flare-meticulousWait for startup to finish, then open http://localhost:3000/auth/login. The fixture administrator is meticulous@example.test with password Flare-test-only-2026!. These credentials and the image's session secret are deliberately public test fixtures; use this image only for isolated local testing.
Dockerfile.meticulous extends the normal app image with PostgreSQL and a process supervisor. Every container start creates a fresh database, runs migrations, seeds the fixture administrator, and starts Flare with its normal authentication guards. PostgreSQL listens only on loopback inside the container. The entrypoint ignores an external DATABASE_URL; no hosted database is needed. Stopping the container discards this test state. Rebuild both images after changing application code.
Replay and compare locally
If needed, install the CLI outside Flare's runtime dependencies:
npm install --global @alwaysmeticulous/cli@latest
meticulous auth whoami
meticulous schema simulateIf authentication is missing, run meticulous auth login in an interactive terminal. OAuth project selection is available through meticulous auth set-project. An existing METICULOUS_API_TOKEN can also authenticate the CLI; keep it in your environment or secret store, never in commands committed to this repository. The public recording token described below is a different credential.
Choose a session recorded against the same disposable fixtures that exercises the changed UI, or record one. Replace SESSION_ID with its actual ID. With a known-good version of the local app running, make a baseline:
METICULOUS_SESSION_ID='SESSION_ID'
meticulous simulate \
--sessionId="$METICULOUS_SESSION_ID" \
--appUrl=http://localhost:3000 \
--headless --takeSnapshots --storyboardSave the replay ID from the command's View simulation at: URL. Run the changed app at the same address, then replay the same session with that baseline:
METICULOUS_BASE_REPLAY_ID='BASE_REPLAY_ID'
meticulous simulate \
--sessionId="$METICULOUS_SESSION_ID" \
--appUrl=http://localhost:3000 \
--baseReplayId="$METICULOUS_BASE_REPLAY_ID" \
--headless --takeSnapshots --storyboardInspect the per-screenshot results, screenshots, and changed DOM metadata under ~/.meticulous/replays/. Diffs are stored under the new replay's diffs/BASE_REPLAY_ID/ directory. Without --baseReplayId, the command captures screenshots for inspection but does not establish that the UI is unchanged. Report the exercised routes, expected changes, unexpected differences, and any failed or incomplete replay. Keep session downloads, replay output, and caches out of Git; a repository-local .meticulous/ directory is excluded from Git and the Docker build context.
For an HTTP --appUrl, the CLI does not configure backend replay inside the running app. These commands combine browser network stubs with server rendering against the live disposable database. Recorded cookies must match the fixture account and session secret, and any server-visible data the flow needs must exist in that database. A browser-stubbed write does not populate the local database. If a session depends on missing state, prepare matching demonstration fixtures or choose another session; an incomplete replay is not a passing visual check. Setting METICULOUS_BACKEND_RECORDER_MODE=replay alone cannot restore recorded backend state because it also needs session-specific mock data.
Record a local session
Flare passes the browser session ID to its backend recorder, which instruments server rendering and Prisma calls. Ordinary production recording stays off unless explicitly enabled. Development/preview recording requires NEXT_PUBLIC_METICULOUS_RECORDING_TOKEN; for local development it can live in gitignored .env.local. Keep recording limited to demonstration data because recordings include interactions, network data, and account context.
To record the disposable production build, stop the first container, export your project's public recording token in your shell, then run:
docker run --rm -p 127.0.0.1:3000:3000 \
-e METICULOUS_RECORDING_ENABLED=true \
-e NEXT_PUBLIC_METICULOUS_RECORDING_TOKEN \
flare-meticulousIn another terminal, run meticulous record session. Sign in with the fixture user in its recording browser, then open http://localhost:3000/dashboard in a new tab of that same browser to start an authenticated recording. Its initial state must include the authentication cookie: a stubbed sign-in response cannot create a real server session for later server-rendered requests. The CLI recorder captures HTTP-only cookies, unlike the page script alone. When driving that browser automatically, call window.Meticulous.record.flush() before closing it so final interactions are uploaded.
For server-rendered replay, the project's Network Stubbing setting must be Stub all requests, apart from requests for server components and static assets. This lets the current app render its own server components instead of returning recorded development-build responses. Backend recordings remain available to tooling configured to use them, but the local HTTP URL recipe above does not automatically inject them. A replay is a simulation using recorded network responses; it does not prove that a real upload, email, or webhook was delivered.
Retire existing hosted CI setup
The former .github/workflows/meticulous.yaml workflow has been removed. Once that change is on the default branch, repository administrators should remove any Meticulous required status checks from branch protection or rulesets, disable any separately configured Meticulous GitHub integration automation, and remove the repository Actions secret METICULOUS_API_TOKEN if nothing else uses it. Already queued runs and external integration settings are not canceled by deleting the workflow file. Local CLI credentials and recording tokens are separate from the retired Actions secret.
Automatic coverage checks
npm run check:coverage checks every API route file against the inventory, verifies all named-token operations, scopes, and current role requirements against openapi.json, checks that every permission key is covered in the roles guide, compares the downloadable webhook schema to its canonical version, and checks that supported environment variables appear in the configuration guide.
Changes to role and user-management components or lib/permissions/ require administration guidance; changes to the permission-to-route map also require the API reference. The gate tests these paths with an isolated Git fixture.
CI also checks each non-merge commit on pull requests and direct pushes to main:
npm run check:changes -- --base BASE_COMMIT_SHAReplace BASE_COMMIT_SHA with the actual hexadecimal commit SHA you are comparing against. The gate requires related documentation areas to change in the same commit as user, administrator, hosting, or API code. A documentation follow-up commit does not cover an earlier feature commit; amend or reorganize the commits before submitting. It cannot verify semantic completeness; reviewers and agents must still use the coverage matrix in AGENTS.md.
Publish the site
The VitePress deployment guide describes the static hosting model. No database, authentication secret, server-side renderer, or app environment variables are needed for this site.
GitHub Pages
The repository includes .github/workflows/docs.yml. On pull requests and pushes it checks and browser-tests the current source, then builds and tests the stable site, release archives, and rolling preview from the same checkout. Pull requests never upload a Pages artifact or deploy. Publishing is opt-in through the repository variable below.
- In repository Settings → Pages, choose GitHub Actions as the source.
- Add the repository Actions variable
DOCS_PAGES_ENABLEDwith the valuetrue. - The workflow defaults to the repository subpath, such as
/Flare/. If you use a custom domain at its root, set the Actions variableDOCS_BASEto/and configure that domain in Pages settings. - Run the Documentation workflow on
main, or push a docs change tomain.
The build job uploads only docs/site/.vitepress/releases/ as the Pages artifact, and the deployment job reports the actual published URL. The root content comes from GitHub's latest stable release, even when the workflow runs after a push containing unreleased application changes. /rolling/ comes from the published rolling commit. The navigation and archive tooling can improve independently while application guidance remains tied to its release or rolling commit.
Publishing runs after a successful Release or Rolling Release workflow from this repository's main, when a stable or rolling release is manually published or edited, on pushes to main, and on manual Documentation runs from main. The workflow_run trigger is necessary because tags and releases created with GITHUB_TOKEN do not trigger another push/release workflow. Failed release workflows, draft releases, and other prereleases do not publish docs. A push may build before rolling images are ready; the successful Rolling Release completion rebuilds the website with the newly published rolling commit. Publishing runs are serialized across trigger types so their builds and deployments cannot overtake one another. A failed build or browser check leaves the previous Pages deployment in place; fix the failure and rerun Documentation on main.
Release events, completed release workflows, and manual publishing use the tooling checked out from main. Source checks and release builds share that one checkout, so an intervening commit cannot change the tooling between validation and building. No checkout reference or build artifact from a pull request or another workflow is used to publish. Git credentials are not persisted, and the workflow does not restore or save dependency caches. Only the separate deployment job receives Pages write permissions, after all checks pass.
Any other static host
Use the repository as the build source, set the build root to the repository root, and configure:
| Setting | Value |
|---|---|
| Node version | 24 |
| Install command | npm ci --prefix docs/site |
| Build command | npm run build:releases --prefix docs/site |
| Publish directory | docs/site/.vitepress/releases |
| Base path | DOCS_BASE=/ for a domain root; otherwise the mounted subpath |
The repository root, complete Git history and tags, and GitHub release API access are required during the build. Screenshots and runnable examples come from the selected stable or rolling source directories. Keep GH_TOKEN, if needed, in the host's build environment; it must not be embedded in the site. Deploy only the output directory afterward. Ordinary .html URLs are intentional: deep links work on basic static hosts without a catch-all rewrite. Configure the host to serve index.html for directory URLs and 404.html for missing pages.
For an existing web server, build locally and copy the output into a dedicated static document root. Do not route documentation requests through the Flare application or expose the repository, .env, source maps containing private code, or build credentials.
Releases and older guides
The versions and changes page lists dated archives for every published stable release and compares their original documentation sources. Dates in the list come from the release's published_at metadata in UTC. They are separate from source commit dates and website build dates. A rebuild can update the website's presentation without changing the archived application documentation.
The same page offers Open rolling docs (unreleased) as an explicit choice. Rolling pages show Rolling preview · Unreleased, an explanation that they describe a rolling build, and Read stable docs to return to the default handbook. Rolling is never selected automatically or remembered as the homepage default. Its update date uses the rolling release's updated_at, because published_at belongs to the first publication of the reused release. Its commit identifies the actual source; its build date identifies the website build. The rolling path is mutable and does not archive every development commit. Rolling pages request noindex; this limits search discovery and is not access control.
Every page displays its Flare version and the source commit that supplied the documentation, with a link to that exact commit. Build details opens the corresponding build-info.json, including the full hash and UTC build time. Release builds identify their tagged content and publishing source separately. Local previews with modified or untracked source show Uncommitted changes; they must not be presented as an exact clean-commit build. Source archives without Git metadata show that the commit is unavailable (or use GITHUB_SHA when a CI archive provides it).
The preview stamp is regenerated by npm run assets, which runs before dev and build; release builds generate provenance for each archived source. No release label, date, or hash needs to be maintained by hand. Keep release-sensitive notes in the relevant guide and tell users where to see their installed version. A version selector links to the matching archive; do not assume current stable instructions apply to an older installation.
The handbook first shipped in 2.1.0. Version 2.0.0 has its original README and nine engineering guides; earlier stable releases have only their README. The archive presents these as legacy documentation, preserving the coverage that existed at the time. It does not claim that later handbook topics or interactive demonstrations existed in those releases. Historical examples may contain mutable latest image tags or external service links; archive readers should use explicit release tags when reproducing an older setup.
Comparisons show added and removed source lines between the selected releases. Handbook snapshots include docs/site/ Markdown, theme components, and downloadable JSON contracts such as OpenAPI; legacy snapshots include README.md and docs/*.md. Comparing a legacy release with a handbook release therefore shows earlier engineering guides as removed and handbook pages as added. These comparisons help locate changed instructions, but do not replace application release notes, migrations, or upgrade guidance. A moved page can appear as a removal and an addition. Preserve historical source content; update present-day explanations in the version browser or the current relevant guide.
The same-commit documentation gate starts when the policy is introduced; it does not retroactively reject commits that predate the handbook.
If a feature is renamed or removed, update navigation and incoming links in the same change. Keep a short redirect/link page when there are established external links. OpenAPI and example clients must change with the implementation; do not promise a versioning policy the server does not implement.