diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca532e20..6ce65087 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,22 +26,25 @@ jobs: - id: changes env: EVENT: ${{ github.event_name }} - BASE: ${{ github.event.pull_request.base.sha }} - HEAD: ${{ github.event.pull_request.head.sha }} + BASE: ${{ github.event.pull_request.base.sha || github.event.before }} + HEAD: ${{ github.event.pull_request.head.sha || github.sha }} + CHANGED_FILES: ${{ runner.temp }}/nmsh-changed-files shell: bash run: | # Never path-skip the workflow: the final CI check must always exist. - # Push/manual runs always collect comprehensive evidence. - if [[ "$EVENT" != pull_request ]]; then + if [[ "$EVENT" == workflow_dispatch || -z "$BASE" || "$BASE" =~ ^0+$ ]]; then echo 'code=true' >> "$GITHUB_OUTPUT" else git diff --check "$BASE" "$HEAD" - git diff --no-renames --name-only -z "$BASE" "$HEAD" > /tmp/changed-files + git diff --no-renames --name-only -z "$BASE" "$HEAD" > "$CHANGED_FILES" python3 - <<'PYTHON' >> "$GITHUB_OUTPUT" + import os from pathlib import Path - files = Path('/tmp/changed-files').read_bytes().split(b'\0') - docs_only = all(f.startswith(b'docs/') or f.endswith(b'.md') for f in files if f) - print('code=' + str(not docs_only).lower()) + files = Path(os.environ['CHANGED_FILES']).read_bytes().split(b'\0') + docs_only = all(filename.startswith(b'docs/') or filename.endswith(b'.md') for filename in files if filename) + website_files = [filename for filename in files if filename.startswith(b'website/') or filename in (b'.github/workflows/website.yml', b'.github/workflows/website-deploy.yml')] + website_only = bool(website_files) and all(filename in website_files or filename == b'README.md' for filename in files if filename) + print('code=' + str(not (website_only or (os.environ['EVENT'] == 'pull_request' and docs_only))).lower()) PYTHON fi @@ -112,7 +115,8 @@ jobs: timings: name: Timing smoke (${{ matrix.os }}, Node 26) - if: github.event_name != 'pull_request' + needs: changes + if: needs.changes.outputs.code == 'true' && github.event_name != 'pull_request' runs-on: ${{ matrix.os }} timeout-minutes: 5 strategy: @@ -135,7 +139,8 @@ jobs: fedora: name: Linux portability subset (Fedora) - if: github.event_name != 'pull_request' + needs: changes + if: needs.changes.outputs.code == 'true' && github.event_name != 'pull_request' runs-on: ubuntu-24.04 timeout-minutes: 15 container: fedora:42 @@ -173,7 +178,7 @@ jobs: required += ['quality', 'tests', 'node22'] elif code != 'false': raise SystemExit('Missing change classification') - if '${{ github.event_name }}' != 'pull_request': + if code == 'true' and '${{ github.event_name }}' != 'pull_request': required += ['timings', 'fedora'] failed = [name for name in required if jobs[name]['result'] != 'success'] if failed: diff --git a/.github/workflows/website-deploy.yml b/.github/workflows/website-deploy.yml new file mode 100644 index 00000000..366ce856 --- /dev/null +++ b/.github/workflows/website-deploy.yml @@ -0,0 +1,101 @@ +name: Publish NMSh website + +on: + workflow_dispatch: + inputs: + confirmation: + description: Type nmsh.raisecaterror.com to request production publication + type: string + required: true + +permissions: + contents: read + +concurrency: + group: nmsh-production + cancel-in-progress: false + +jobs: + build: + if: github.repository == 'raiseCatError/notMyShell' && github.ref == 'refs/heads/master' && inputs.confirmation == 'nmsh.raisecaterror.com' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + defaults: + run: + working-directory: website + outputs: + artifact_id: ${{ steps.site.outputs.artifact-id }} + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 + with: + ref: ${{ github.sha }} + persist-credentials: false + - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 + with: + node-version: 22.22.0 + cache: npm + cache-dependency-path: website/package-lock.json + - run: npm ci + - run: npm test + - run: npm run check + - run: npm run build:launch + env: + GITHUB_TOKEN: ${{ github.token }} + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 + id: site + with: + name: nmsh-website + path: website/dist + include-hidden-files: true + if-no-files-found: error + retention-days: 1 + + deploy: + needs: build + runs-on: ubuntu-24.04 + timeout-minutes: 10 + environment: + name: nmsh-production + defaults: + run: + working-directory: website + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 + with: + ref: ${{ github.sha }} + persist-credentials: false + - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 + with: + node-version: 22.22.0 + cache: npm + cache-dependency-path: website/package-lock.json + - run: npm ci --ignore-scripts + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 + with: + artifact-ids: ${{ needs.build.outputs.artifact_id }} + path: website/dist + merge-multiple: true + - name: Recheck the published release after approval + run: node scripts/validate-content.mjs --require-published && npm run check:upstream + env: + GITHUB_TOKEN: ${{ github.token }} + - name: Verify manual-only settings and publish the approved artifact + env: + CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + WRANGLER_SEND_METRICS: 'false' + run: | + node --input-type=module <<'NODE' + const account = process.env.CLOUDFLARE_ACCOUNT_ID; + const token = process.env.CLOUDFLARE_API_TOKEN; + if (!/^[a-f\d]{32}$/.test(account || '') || !token) throw new Error('Production environment credentials are missing.'); + const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${account}/pages/projects/nmsh`, { headers: { Authorization: `Bearer ${token}` } }); + const payload = await response.json(); + const project = payload.result; + const source = project?.source; + const config = source?.config; + const correctTarget = project?.name === 'nmsh' && project.production_branch === 'master' && project.build_config?.root_dir === 'website' && source?.type === 'github' && config?.owner === 'raiseCatError' && config.repo_name === 'notMyShell'; + const manualOnly = config?.production_deployments_enabled === false && config.preview_deployment_setting === 'none' && config.deployments_enabled === false; + if (!response.ok || !payload.success || !correctTarget || !manualOnly) throw new Error('Pages target or manual-only settings changed. Stop and review the project.'); + NODE + npx --no-install wrangler pages deploy dist --project-name nmsh --branch master --commit-hash "$GITHUB_SHA" diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml new file mode 100644 index 00000000..bc83ff73 --- /dev/null +++ b/.github/workflows/website.yml @@ -0,0 +1,37 @@ +name: Website + +on: + push: + branches: [dev, master, feature/nmsh-website] + paths: ['website/**', '.github/workflows/website.yml', '.github/workflows/website-deploy.yml'] + pull_request: + paths: ['website/**', '.github/workflows/website.yml', '.github/workflows/website-deploy.yml', '.github/workflows/ci.yml'] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: website-${{ github.ref }} + cancel-in-progress: true + +jobs: + website: + name: Static website checks + runs-on: ubuntu-24.04 + timeout-minutes: 5 + defaults: + run: + working-directory: website + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22.x + cache: npm + cache-dependency-path: website/package-lock.json + - run: npm ci + - run: npm test + - run: npm run check + - run: npm run build + - run: git diff --check diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 00000000..bb0c5c1f --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +dist/ +.astro/ +.cache/ +playwright-report/ +test-results/ diff --git a/website/DESIGN.md b/website/DESIGN.md new file mode 100644 index 00000000..a794b7cb --- /dev/null +++ b/website/DESIGN.md @@ -0,0 +1,150 @@ +# NMSh website specification + +## Brief and constraints + +Build the official product and documentation site for notMyShell by raiseCatError at +`https://nmsh.raisecaterror.com`. The site explains a frontend over a persistent real +shell, not a shell, terminal emulator, multiplexer or AI-first terminal. It must be +reviewable and deployable independently of the CLI. No capture, recording, demo, +terminal simulation or media-generation work is permitted. All media is pending. + +The user authorizes autonomous design and inline implementation, without intermediate +approval gates. No publishing, merging, release creation or production configuration. +Keep the dedicated external worktree intact. Only website files and narrowly scoped +CI filtering may change. The later branding addendum explicitly authorizes Vespyr +favicons and one static social-preview composition, not product captures. + +## Discovery (2026-10-17) + +- Latest public stable release: v0.18.0, published 2026-10-13. Physical-terminal QA + for this release remains pending; Linux and WSL 2 have additional validation gaps. +- The public Homebrew formula still references v0.17.0. Surface that discrepancy; + do not imply the tap necessarily installs the latest release. +- `origin/dev` is 4a02520d (older than stable `origin/master`, 9ec06768). Branch + from dev as instructed, without bringing application/release changes into this PR. +- Existing status-strip, onboarding, release and older website worktrees are not + reused or modified. `nmsh-site` is not used or deleted. +- Sources reviewed: stable README, architecture, roadmap, changelog, contributor, + support, security, license, llms, platforms, ShellAdapter, prompt customization, + context modules, accessibility, session design, privacy, demos and physical QA. +- Issue #398 is a future proposal, not a release commitment. Bash/zsh only initially; + verified OS-enforced isolation or explicitly labeled simulation, never a temp-folder + "sandbox". Architecture/security/curriculum approval remains a gate. + +## Architecture and alternatives + +Use Astro static output, typed Markdown collections, Astro's built-in Shiki and +Pagefind's local static index. No React, MDX, UI framework, analytics, CDN fonts, +server adapter or client-side router. Compared with Starlight, custom layouts keep +the product and docs visually continuous without overriding a framework's UI. +Compared with hand-built HTML, Astro supplies safe content rendering and route +generation with less custom machinery. Dependencies stay in website/package.json. + +Curated beginner guides link to canonical release documents. Longer technical +references are generated, verbatim, from a pinned stable Git commit through an +explicit upstream-sync command. A provenance manifest records paths and hashes. +Normal builds never fetch GitHub or require git history or CLI compilation. Release +notes are cached from real public releases. Upstream changes are reviewed, never +silently promoted by a build. A check-only sync reports drift without changing files. + +## Information architecture + +Product `/`, Features `/features/`, Install `/install/`, Docs `/docs/`, future Learn +`/learn/`, Roadmap `/roadmap/`, Releases `/releases/`, Architecture `/architecture/`. +Docs progress from first run, concepts and everyday use through composer, completion, +transcript, sessions, prompts, themes, integrations, context, shells, configuration, +accessibility, troubleshooting, advanced usage, development and reference. +Reference documents remain visibly versioned and retain their historical context. + +## Visual system + +Charcoal canvas, graphite surfaces, warm off-white text, quiet lavender emphasis. +Light mode is warm paper, dark ink and deeper violet. Dark is the first-visit default; +manual choice persists when storage is available. System UI typography avoids font +requests; monospace is reserved for commands, metadata and concise labels. Headings +use tight tracking, measured line-height and weight rather than oversized effects. +Maximum marketing width 1160px; prose 70ch; 8px-derived spacing, 1px rules, small +4–12px radii only where useful. No gradient blobs, glass, fake terminal chrome or +decorative animation. Original brand artwork is reused unmodified. + +Marketing sections use editorial splits, numbered explanations and comparison rows, +not endless cards. A fine ruled media field with an abstract registration mark, +purpose label and honest pending notice reserves its real aspect ratio. No fake +commands, output, playback buttons or product UI. Learn is a conceptual introduction, +not a functional lesson player. The homepage stays static. Motion is limited to quick +article-only fades between documentation pages, short native dialog entry/exit and +hover feedback. No scroll-triggered reveals or JavaScript-gated content. Reduced +motion disables it. Focus rings and text labels carry state +independently of color. + +## Responsive and accessible behavior + +Desktop: persistent global header, docs sidebar, prose and optional sticky TOC. +Tablet: sidebar plus prose; TOC collapses to disclosure. Mobile: native navigation +disclosures and a one-column reading layout; no unusably narrow content columns. +Native buttons, links, details and dialog; escape closes search; focus returns to +its trigger. Skip link, anchor links, breadcrumbs, previous/next docs, live feedback +for copy/search, scrollable labeled code/tables. Search is progressive enhancement; +the complete docs index remains usable without JavaScript. Copy failure explains +manual selection. There are no essential hover-only interactions. + +## Content and media contracts + +Stable version, publication date, tag commit, source-install command and Homebrew +version come from one snapshot. Released, in-development and planned are explicit. +No popularity claims, deadlines, installers, testimonials, pricing or invented team. +Media manifest fields: semantic ID, purpose, aspect ratio, dimensions, caption, +future alt-text requirement, optional image/video source and poster. Replacement +requires genuine supplied media, specific alt text and retained captions. Video uses +native controls, never autoplay. Pending slots are shippable, not broken images. + +## Verification contract + +Node tests for release validation, source-link resolution and media validation. +Astro check and static build; post-build crawl for links, anchors, metadata, media, +search index and bundle budgets. Browser checks at desktop/tablet/mobile: navigation, +search, theme persistence, copy feedback, keyboard, no overflow, reduced motion, +contrast and automated accessibility. These checks never launch NMSh or create +terminal media. Manual human visual/product review and Cloudflare setup remain. + +## Refinement and launch correction (2026-10-17) + +The intended baseline is now the cumulative v0.20 product, not the last published +v0.18 release. `LAUNCH.md` records exact source ancestry, integrated feature coverage, +onboarding gaps and publication gates. Candidate provenance and actual public release +history are separate; generated references follow the candidate checkpoint. Publication +requires a real immutable released tag, final content review and explicit approval. + +Primary hook: **A richer way to use the terminal.** Supporting copy describes command +editing, output, queueing and live sessions before clarifying the real-shell model. +Authored copy uses natural punctuation and concrete descriptions, without em dashes. +Canonical snapshots, code and actual historical release notes retain their text. + +Preserve the existing editorial splits, reading measure, neutral palette and honest +media fields. Refine hierarchy rather than adding cards or decorative effects. The +hero aligns headline and supporting copy at their top edges, including tablet sizes. +Primary actions have one clear visual priority; GitHub remains a quiet secondary link. + +Header: the exact existing pixel Vespyr silhouette plus plain notMyShell text. No +miniature duplicate wordmark. Personality is limited to this brand signature and a +short footer aside. Search uses a compact icon/label/key hint and a softly rounded, +top-anchored native dialog. This is the exception to the site's sharper geometry; +results grow downward without moving the input after entry. + +Docs keep ordinary full-page links, native Back/Forward and anchor behavior. Only +the independently scrolling sidebar position and native category disclosures persist +in session storage, restored before article parsing. All categories and nested groups +start expanded. The active article's ancestors always open; unavailable storage +falls back to the expanded server-rendered navigation. Native cross-document view +transitions animate only the article, without a client router or whole-page fade. +Different article names avoid size/position morphing. Unsupported browsers and +reduced motion switch immediately; same-article anchors never replay an entrance. +Clicking the current item does not reload the article. A passive section indicator +follows reading position without scrolling or rewriting history. Eight-pixel square +scrollbars use theme-specific graphite and lavender colors with a native forced-color +fallback. Scrolling itself remains native. + +The editable 1200×630 social composition uses charcoal, neutral typography, the +lavender hook and genuine Vespyr. It is branding, never a simulated terminal. SVG +geometry is extracted from the approved lockup; deterministic Sharp exports are +checked in and are not rebuilt by ordinary site builds. diff --git a/website/LAUNCH.md b/website/LAUNCH.md new file mode 100644 index 00000000..0cdff701 --- /dev/null +++ b/website/LAUNCH.md @@ -0,0 +1,128 @@ +# v0.20 launch coverage and remaining gates + +## Source checkpoint + +This is the **v0.20 launch website**, not a v0.18 product site. The handbook and +technical references use public candidate commit +`15d776eeb242cda330bbe654efae7fbac744b58d`, the public onboarding checkpoint. +It includes stabilized Slice 3 at `825336393562a37cb5cdf9baf916e8d6d3bb4e6d` +plus production-wired Reactive Horizon. The pin is immutable rather than following +another agent's moving feature branch. +`src/data/launch.json` controls that baseline. Public releases and Homebrew availability +are independently recorded in `src/data/release.json`; no v0.20 publication is invented. + +Read-only ancestry inspection, refreshed 2026-10-18, established that it includes: + +| Source branch | Inspected commit | +| --- | --- | +| `release/v0.19.0` | `9e85a9100d1a377ccb3b270c99d98cbae0ff2605` | +| `stabilize/v0.19.0` | `1aeb05c0d569c06562e8fa9219eae3069686e0cd` | +| `release/v0.20.0` | `c71a42846da4a3832e578f3fb9c28f192510d08e` | +| `stabilize/v0.20.0` | `891ee5835b956952ae5c15e1929e8dab22908d7d` | +| `integration/post-v020-slice3` | `15cb52fef3e2f4397f67cb2a79fe2c9ca98c1feb` | +| `stabilize/post-v020-slice3` | `825336393562a37cb5cdf9baf916e8d6d3bb4e6d` | +| Public onboarding feature checkpoint | `15d776eeb242cda330bbe654efae7fbac744b58d` | + +These are source checkpoints, not extra published releases or promises that every +proposal inside their historical documents is implemented. The user explicitly +includes cumulative integrated work in the intended launch scope; the final release +tag must be checked again rather than inferred from its name. + +## Feature coverage inventory + +“Integrated” below means inspected production code and corresponding regression +sources exist at the checkpoint. It does **not** mean this website task reran the +application suite or performed physical terminal acceptance. Recorded local +`verify:release` evidence is in `docs/development/slice-3-stabilization.md`; that +record identifies exact-head remote and physical checks separately. The onboarding +plan also records completed implementation checks and a benchmark cleanup failure +followed by a successful rerun. None establishes final released-tag physical acceptance. + +| Capability | Integration and evidence at the checkpoint | Website coverage | +| --- | --- | --- | +| Persistent real zsh, Bash 4.4+, Fish; composer and semantic input | Integrated; `src/session/`, `src/shell/`, ShellAdapter architecture | Home, concepts, composer, completion, shells | +| Transcript, history, live sessions and resume | Integrated; runtime architecture, session interaction contracts | Transcript, sessions, architecture | +| Serial queue and input ownership | Integrated; `tests/queueControlsApp.test.ts`, `src/commands/slashCommands.ts` | Execution guide and command reference | +| Live timeline and queue settings | Integrated; `src/prompt/configuration.ts`: On, two recent/two upcoming, eight-second retention | Execution guide | +| Queued output folding | Integrated; `tests/queueOutputFolding.test.ts`; Inline default, optional Fold, reversible Ctrl+O | Execution and transcript guides | +| Queue summaries | Integrated; `tests/queueHistory.test.ts`, `tests/queueHistoryApp.test.ts`; fresh Recommended On, legacy omitted field Off | Execution guide; not executable restored work | +| Output selection and Copy Actions | Integrated Slice 3; `tests/outputSelectionApp.test.ts`, `tests/copyFolding.test.ts` | Transcript; bare `/copy` navigates, explicit latest/picker remain | +| Reviewed reports, Compare, pins, clips and paste review | Integrated; `src/ui/ReportReview.ts`, `src/ui/ComparePanel.ts`, `src/input/BatchReview.ts`, slash registry | Transcript, everyday usage, execution, command reference | +| Background tasks | Integrated; `tests/backgroundIntegration.test.ts`, `tests/backgroundSession.test.ts`, supervisor tests | Execution; separate PTY/output, no aliases/functions, service requirement | +| Managed Claude Code and native model/effort controls | Integrated beta; `tests/agentControlPlane.test.ts`, `src/agents/input/pickers.ts` | Integrations; requested versus observed state | +| Other agents and mods | Observation/inventory only; Codex adapter not frontend-wired; Claude plugin toggle gated | Integrations; no managed Codex/OpenCode parity claim | +| Worktrees and GitHub | Integrated; `tests/worktreeMutations.test.ts`, `tests/githubWorkspace.test.ts` | Integrations; staged navigation, non-forced removal, read-only GitHub | +| Workspace regions | Integrated experimental, Off by default; `tests/workspaceExperimental.test.ts` | Features and integrations; independent commands still work | +| Native modules and Context Packs | Integrated; `src/context/packs/builtin/`, context-module architecture | Context guide; more runtimes/build tools, no executable packs | +| Prompts, Theme Studio, Chroma and Theme Bridge | Integrated; prompt/theme ownership contracts | Prompts, themes, configuration, accessibility | +| Rerunnable Setup | Integrated; `src/setup/SetupCat.ts`, `tests/setupOrder.test.ts` | Setup guide; starting points versus full guided wizard | +| Reactive Horizon first run | Integrated into production app paths at public `15d776ee`; `tests/startupApp.test.ts`, `tests/startupEnding.test.ts` | Setup guide; four choices, reactive sky, transparency, real-composer ending and bounded host offer | +| Learn Mode | **Future proposal**, issue #398; not part of v0.20 code | Dedicated planned Learn page; isolation gates preserved | + +## Onboarding integration boundary + +An earlier audit at local `4efac992` correctly found only pure sky modules and the +old modal. That finding was superseded during website work: public `15d776ee` now +contains the actual renderer, `/startup`, bounded clock, guided restyle, Vespyr +compositing and real-composer ending. The website was repinned after inspecting +those production callers and tests, not merely the specification. No other branch +or worktree was merged, modified or pushed by this task. + +The unified pin also includes fresh Recommended defaults: Rich motion, Chat, +Grayscale history, Session title and automatic typed queue summaries On. Legacy +partial saved objects retain historical fallbacks. Minimal starts Two-line and +keeps a quiet Flow/Normal setup. Exact behavior is in the curated Setup guide; +the canonical QA snapshot can contain historical notes that need final reconciliation. + +At the final read-only check, the active local onboarding branch had advanced to +`efc02de7ff8814fb32e60c117d2fe46b35843727`, beyond its public `15d776ee` remote. +These newer follow-ups are **not** silently documented as part of the current pin: + +- `6f8e9993`: first-run welcome for every choice, extra Keep Existing provider choices. +- `4edc8379`: fixed wizard frame and editor-return behavior. +- `0a37de9f`: unified optional-tools review/install flow. +- `720a40c5`: expanded custom-color editor and presets. +- `43cbbfd6`: installation-specific amber update notice. +- `f2f6a2d9`: unified selection glyphs. + +The current public pin welcomes genuine first-run Recommended only; other Apply +paths produce a receipt. Optional tools are not detected or installed by the +starting-point selector at this pin. Those are specific follow-up checks, not a +reason to call the already integrated Horizon experience a future proposal. + +## Release synchronization (2026-10-18) + +- v0.20.0 is published: tag `v0.20.0` → `65e4de55b2d8f1ad3a734a9d415a353b56b81e2c` + (merge of release PR #392; tree identical to the verified release commit `efcaa9d0`). + It includes every source checkpoint above and the newer onboarding follow-ups listed + earlier (`6f8e9993` … `a469ba8e`). v0.19.0 was never published. +- `npm run sync:upstream -- --published v0.20.0 --version 0.20.0` repinned every + technical reference to the tag and imported the real release history (11 public + releases, no v0.19.0). `launch.json` is `published`; candidate `noindex` is gone + (only the 404 keeps it). +- Homebrew: the tap formula targets v0.20.0 (tap PR #3, `brew test-bot` green on + macOS and Linux). +- Curated guides and pages were reconciled against the release: welcome on every first + run, Keep Existing provider choices, the Tools tiers and flow, the update notice, + queue settings and history, platform evidence. Candidate-only wording is state-gated + or removed. Learn Mode stays a planned proposal. +- Checks on the synced tree: `npm test`, `npm run check`, `npm run build:launch` + (62 pages, internal links/anchors, sitemap, metadata, search), `npm run test:browser` + (17/17, axe in both themes and reduced motion). External links: 198 of 199 return + 200; the private GitHub Project board was removed from the roadmap page and remains + only inside the canonical Contributing snapshot. +- Known snapshot wording: canonical references reproduce repository documents at the + tag, including historical notes (for example `context-modules` still calls the v0.18 + stack pending). They change only through a reviewed application-docs update and the + next sync. + +## Publication gates + +- [x] Real public v0.20.0 release and immutable tag verified; published sync done. +- [x] Homebrew formula independently checked against the tag. +- [x] `check:upstream`, `build:launch` and browser checks pass on the final tree. +- [ ] Integrate through the normal PR process to `master` (the publish workflow only + runs from `master`). +- [ ] Dispatch **Publish NMSh website** and obtain the maintainer's `nmsh-production` + approval. Then check the Pages hostname, attach `nmsh.raisecaterror.com` with + separate approval, and verify the live site before activating outgoing links. diff --git a/website/PLAN.md b/website/PLAN.md new file mode 100644 index 00000000..ac552992 --- /dev/null +++ b/website/PLAN.md @@ -0,0 +1,47 @@ +# NMSh website implementation plan + +**Goal:** Deliver a complete static product/docs site with honest media placeholders. +**Architecture:** Astro Markdown collections, custom shared layouts, Pagefind search, +checked-in public-release/source snapshots, no CLI runtime dependency. +**Spec:** DESIGN.md. Execute inline; user explicitly requests autonomous completion. + +## Global constraints +- Source under website/, independent install/build/test. Node >=22.22.0 for Astro. +- No captures, recordings, demo tooling, app launches or production deployments. +- Worktree on verified T7, feature/nmsh-website from origin/dev; leave it intact. +- Launch target is v0.20.0, currently a verified candidate checkpoint; published + release history remains separate. `LAUNCH.md` records source coverage and gates. + +## Review focus +- Offline external data: builds use reviewed snapshots, never live network requests. +- Narrow screens and long reference tables: preserve readable text without overflow. +- Missing media or failed clipboard/storage: no broken UI or false success. +- Release/source drift: check-only refresh fails without silently updating the site. +- Canonical Markdown relative links: resolve against the pinned source, not website paths. + +## Task 1 — source and content foundation +- [x] Write failing Node tests for stable-release selection, source links and media. +- [x] Run them and confirm failure, then implement small data helpers. +- [x] Add isolated package/config, upstream sync and reviewed provenance snapshots. +- [x] Author concise guides and generate canonical technical references. +- [x] Verify tests and commit source/content foundation (`d97e878f`). + +Interfaces: release.json supplies latest/history/tap/sourceCommit; upstream.json +maps canonical paths to site reference slugs and sha256; media.ts supplies typed slots. + +## Task 2 — product and documentation experience +- [x] Write browser assertions for navigation, themes, search, copy and missing media. +- [x] Implement shared layout, visual tokens, product pages and Markdown docs routes. +- [x] Add native mobile navigation, TOC, anchors, metadata, sitemap, robots and 404. +- [x] Build Pagefind index and progressively enhanced search; no hydration framework. +- [x] Verify typecheck/build/link crawl and browser behavior, then commit (`365cbf89`). + +Interfaces: Base supplies header/footer/theme/copy/search; Docs supplies sidebar, +TOC and previous/next; Media reads semantic slot IDs. CSS tokens serve both themes. + +## Task 3 — integration and delivery +- [x] Add inexpensive website-only CI and narrowly exclude website from CLI jobs. +- [x] Document maintenance, refresh, media inventory and Cloudflare Git settings. +- [x] Run full website verification and independent final review; fix regressions. +- Delivery gate: commit and push only feature/nmsh-website after verification. No merge or deployment; confirm the remote checkpoint in the final handoff. +- [x] Record evidence and human review items in REVIEW.md. diff --git a/website/README.md b/website/README.md new file mode 100644 index 00000000..3c21a4ba --- /dev/null +++ b/website/README.md @@ -0,0 +1,381 @@ +# The official NMSh website + +Static product site and documentation for **notMyShell**, by **raiseCatError**. +Canonical origin: **https://nmsh.raisecaterror.com**. Source stays in this repository; +the unused `nmsh-site` repository is not involved. Nothing here publishes itself. + +## Architecture and structure + +Astro generates HTML. Its official unified Markdown processor runs small source-link +and accessible-markup transforms; Shiki highlights code at build time. Pagefind builds +a local, lazy-loaded docs search index. There is no React, hydration framework, server +adapter, app bundle, analytics, external font, account system or capture pipeline. + +```text +website/ + src/ + components/ Code blocks, media slots, native search dialog + brand/ Editable social-preview SVG composition + content/guides/ Curated user handbook (editable Markdown) + content/reference/ Generated canonical release documents (do not edit) + content/releases/ Generated actual public release notes + data/ Launch baseline, public releases, source hashes, media inventory + layouts/ Shared product shell and documentation layout + lib/ Shared release metadata and docs navigation + pages/ Product, install, Learn, roadmap, docs and release routes + scripts/ Small browser enhancement: theme, copy and search + styles/ Shared tokens, responsive layouts and prose styles + public/ Genuine brand artwork, robots and Cloudflare headers + scripts/ Explicit upstream refresh and static validation + tests/ Content/CI tests and browser accessibility/interaction checks +``` + +`DESIGN.md` records the brief, discovery and visual specification. `PLAN.md` tracks +implementation; `REVIEW.md` records delivery evidence and review limits. Keep ongoing +maintenance instructions here rather than copying them into those records. +`LAUNCH.md` contains the v0.20 coverage inventory and the short release/onboarding +checklist, with exact inspected commits and explicit integration gaps. + +## Local setup and preview + +The **website** requires Node **22.22.0+** (Astro's requirement), npm and, for browser +tests, installed Google Chrome. The **NMSh application** separately requires Node 22+. +Do not run the repository-root install or application build to work on the site. + +On the maintainer's Mac, use the dedicated T7 worktree and storage launcher: + +```sh +cd /Volumes/T7Tmp/ai-dev/worktrees/notMyShell/nmsh-website/website +rtk t7run --check +rtk t7run npm ci +rtk t7run npm run build +rtk t7run npm run preview -- --port 4321 +``` + +Open **http://127.0.0.1:4321/**. `npm run dev` offers live editing; search needs a +production build because Pagefind indexes generated HTML. Browser tests own port +4321 by default. To leave an existing preview running, use a free port instead: +`rtk t7run env WEBSITE_TEST_PORT=4323 npm run verify`. Do not delete this worktree after review. + +Elsewhere, in a supported development environment, enter `website/`, run `npm ci`, +`npm run build` and `npm run preview`. Local AI-driven dependencies, caches, `.astro`, +`dist` and browser profiles belong on the verified external workspace. This worktree +is already external; `t7run` redirects scratch and shared caches. Never fall back to +the internal SSD if its storage check fails. CI/Cloudflare use their hosted ephemeral +build environment; they do not depend on local paths, credentials or T7 tooling. + +## Commands and validation + +| Command | Purpose | +| --- | --- | +| `npm run dev` | Local live-editing server | +| `npm test` | Content helpers, Markdown transforms and real workflow classifier | +| `npm run check` | Astro and TypeScript diagnostics | +| `npm run build` | Validate provenance, static build, Pagefind, link/anchor/SEO/budget checks | +| `npm run build:launch` | Reject candidates, verify published-source freshness online, then build | +| `npm run brand` | Export authorized Vespyr icons and static social image; no product media | +| `npm run preview` | Serve the built site, including search | +| `npm run test:browser` | Chrome responsive, theme, clipboard, keyboard, no-JS and axe checks | +| `npm run verify` | All website checks including browser tests | +| `npm run check:upstream` | Network check for candidate/release/tag/tap drift; never writes | +| `npm run sync:upstream` | Refresh the chosen launch source and actual release history; review required | + +Builds force a fresh content layer so Markdown-transform changes cannot leave stale +rendered links in Astro's cache. Static checks cover every generated HTML page's +internal links/anchors, one h1, canonical URLs, social metadata, sitemap inclusion, +no autoplay, local image contracts and a 15KB gzip shared-JS budget. Search's own +engine/index is loaded separately, only after a search. The site has no runtime API. + +Browser tests use a fresh Chrome profile under the launcher's external TMPDIR, never +the user's signed-in profile. Tests disable screenshots, video and traces. Any +website-only visual-QA images are disposable external scratch—not product media. +No command launches NMSh or creates terminal captures. Automated axe checks are not +a comprehensive human accessibility audit or physical-terminal validation of NMSh. + +## Content and source-of-truth strategy + +User guides are a curated explanation layer, not a second invented specification. +Each has `title`, `description`, `group`, `order` and `sources` frontmatter. Sources +link directly to the verified launch-source Git commit. Add a `.md` file in +`src/content/guides/`; navigation, previous/next links, sitemap, llms and search update +from the collection. Group names are in `src/lib/docs.ts`. Do not add empty sections. + +Longer technical documents are generated from canonical Markdown in this repository +at the independently verified launch commit. The source list lives in +`scripts/upstream-sources.mjs`; `src/data/upstream.json` records original and generated +SHA-256 hashes plus route mappings. Import removes the first h1 because the layout +supplies its own title, normalizes trailing whitespace/EOF, and preserves Markdown +hard line breaks as explicit `
` elements. Rendering remaps relative Markdown +links to local imported references when available, otherwise the pinned GitHub source. +Historical release-note links stay at their own tag. Do not edit generated snapshots. + +This deliberate versioned mirror makes a shallow Git clone and an **offline build** +work even when `dev` is older than the launch candidate. It does not falsely promote +a candidate to a published release. Historical design records retain their caveats and have an explicit +source-snapshot notice. Curated guides explain current behavior; the presence of a +proposal inside an old design document is not a release promise. + +### Preparing and finalizing the v0.20 launch + +`src/data/launch.json` is the product/documentation baseline: state, intended version, +public ref, immutable commit and verification date. Candidate metadata has no invented +publication date or release URL. `src/data/release.json` independently records actual +public releases and the tap. Generated references must agree with the launch manifest, +not necessarily with the latest published version. This keeps v0.20 product writing +confident without describing a candidate as a released download. + +1. Run `npm run check:upstream`. If GitHub is unavailable or rate-limited, normal + builds remain offline and buildable. The freshness check and publication build + fail closed rather than guessing. An optional `GITHUB_TOKEN` increases API limits. +2. After inspecting an approved public integration checkpoint, refresh explicitly: + `npm run sync:upstream -- --candidate --version 0.20.0`. + This resolves the exact GitHub commit, fetches canonical documents and genuine + brand sources, and refreshes only actual published release history and tap data. + No application branch is checked out, merged or compiled. See `LAUNCH.md` before + replacing onboarding controls or defaults with claims from an unfinished branch. +3. Review reference diffs, guides, defaults, feature coverage, known limits, platform + evidence and installation instructions. The sync is a source refresh, not a + semantic audit. Requirements remain explicit maintained values. The QA source in + `upstream-sources.mjs` must match the actual release, not a renamed old checklist. +4. Once the official public stable v0.20.0 release exists, run + `npm run sync:upstream -- --published v0.20.0`. It rejects a missing, draft or + prerelease target, resolves the immutable tag commit and imports the real notes + and date. Review the final source integration and checklist before continuing. +5. Verify the tap separately. Until it really provides v0.20, keep its discrepancy + visible. Candidate source installs use an exact detached SHA; published installs + switch to the verified tag. `/update` targets stable releases, not candidate review. +6. Finish `LAUNCH.md`, run `npm run verify` and `npm run build:launch`, then seek + publication approval. The latter rejects candidate state and also performs the + online freshness check. Candidate HTML is `noindex` and omits released-version + structured data; verified publication restores normal indexing automatically. + +Check-only mode compares public release titles, bodies, dates, URLs and the historical +set as well as the launch ref/commit and tap. Notes are hash-validated during builds. +Historical notes are never rewritten to match the website voice or launch version. + +Generated collections must exactly match their provenance manifests. An explicit +sync prunes a removed/renamed generated page only when its old recorded hash still +matches; unknown or independently edited orphan files fail closed for review. All +network reads finish before snapshot writes begin, so an unavailable source does +not leave a half-fetched refresh. Filesystem write failures still require checking +the Git diff and rerunning the refresh; normal builds validate the complete result. + +The roadmap is a curated editorial view linked to public issues, not an API dump. +Recheck issue state and product direction during each release review. Issue #398 is +authoritative for Learn Mode; it remains a future proposal with no date or version +commitment. Never add an install command, fake course player or unsafe host-shell demo. + +Add product pages in `src/pages/`, reuse `Base`, `Code` and `Media`, and update sitemap +routes/global navigation deliberately. New or renamed docs paths need explicit +`public/_redirects` entries if old public URLs exist; no migration aliases are invented +for this new site. Existing NMSh canonical documents and history remain untouched. + +## Media inventory and replacement + +**Do not generate the media in this phase.** `src/data/media.json` is the single +inventory and replacement configuration. Each entry specifies purpose, dimensions, +caption and future alt-text requirements. Every slot is currently intentionally empty. + +| Semantic ID | Future subject | Dimensions / ratio | +| --- | --- | --- | +| `home-overview` | Composer, genuine output and command feedback | 1440×900 / 8:5 | +| `composer-input` | Multiline input and semantic highlighting | 1440×900 / 8:5 | +| `transcript-history` | Command blocks, folding, search, plain copy | 1440×900 / 8:5 | +| `theme-studio` | Theme Studio and its real preview | 1440×960 / 3:2 | +| `session-resume` | Detach and reattach a live shell | 1440×900 / 8:5 | +| `context-modules` | Native prompt, Rail and Status Strip | 1440×900 / 8:5 | +| `vespyr-appearance` | Vespyr and appearance choices | 1440×900 / 8:5 | +| `learn-concept` | Future approved guide/practice concept | 1440×960 / 3:2 | + +When genuine supplied assets are available, put them in `public/media/` and add +`src: "/media/name.webp"`, `kind: "image"`, and a specific `alt` to the corresponding +entry. PNG, WebP and AVIF work through normal image elements. For video use +`kind: "video"`, a local `src`, `poster` and descriptive `alt`; native controls, +`preload="none"` and no autoplay are mandatory. Add a text transcript/captions when +the actual recording needs them; the existing `caption` is not a speech transcript. +No video library, automatic playback or animation bypass is introduced. Match the +reserved ratio (the home slot is height-capped on desktop) to avoid layout movement. + +Update the caption honestly when replacing pending media. Do not call a conceptual +Learn image a working interface. Requirements in `altRequirement` are instructions +for a future author, never fabricated alt text for an unavailable asset. Build +validation rejects missing local assets, missing alt text, bad dimensions and +unapproved external media URLs. Genuine existing logo/lockup assets are copied +unchanged from the pinned source, not newly generated terminal imagery. + +### Authorized brand assets + +`npm run brand` extracts the exact rect-based Vespyr sprite from the approved +`public/brand/nmsh-lockup.svg`, originally sourced from `assets/brand/nmsh-lockup.svg` +and the production `catSprite.ts` artwork. It does not invent a new cat or use the +embedded wordmark bitmap. Header `vespyr.svg` keeps its natural wide proportions; +`favicon.svg` centers it on a transparent square. The transparent 16px/32px PNGs +provide tab fallbacks; the 180px PNG is the Apple touch icon. No filled bounding box. + +The editable composition is `src/brand/social-preview.svg`. Sharp, an explicit +development-only dependency already used by Astro, substitutes genuine Vespyr and +exports `public/brand/social-preview.png` at 1200×630. Text uses the local Arial or +Helvetica installation; the checked-in production PNG is the reviewed output, so +ordinary builds neither rasterize artwork nor depend on the maintainer's fonts. +After a deliberate edit, regenerate on the approved workstation, visually inspect +the full image and a small thumbnail, then run the asset tests and website build. +This branding-only command is not a terminal capture or demonstration system. + +## Theme, accessibility and metadata + +Shared CSS tokens define charcoal/lavender dark mode and warm-paper/deep-violet light +mode. Dark is the first-visit default. A small prepaint script reads `nmsh-theme`; +the manual toggle persists it when storage is available and remains functional when +storage writes are blocked. No JS means readable default-dark HTML. Reduced-motion +CSS disables transitions; videos never start themselves. + +The homepage is static. Only documentation article navigation opts into native +cross-document view transitions in `Docs.astro`: a 100ms exit fade and a 180ms entry +fade with a four-pixel upward movement. Unique article names prevent geometry +morphing between differently sized pages. Header, sidebar and root are not animated. +Direct loads and same-article anchors do not replay the transition. Reduced motion +and unsupported browsers use immediate native navigation; no router or animation +dependency is added. Dialog entry/exit uses native discrete CSS transitions with a +plain native fallback. Only search has softer corners. Docs never reveal on scroll. +All sidebar categories and nested groups start expanded, including without JavaScript. +Their choices and desktop scroll offset persist per tab in session storage; active +ancestors always open. The v2 preference key resets the old implicit collapsed defaults +once. Before article parsing, restore the desktop offset and adjust it only if the +active link is outside the visible sidebar. Opening the mobile menu also reveals +its active link without scrolling the article. +`src/lib/docs.ts` groups existing references into nested categories and rejects an +uncategorized new reference. A passive TOC indicator tracks the current section; +article scrolling, browser Back/Forward and heading hashes remain native. Blocked +storage stays usable. CSS supplies thin, square theme-colored scrollbars through +standard properties and compatible eight-pixel WebKit styling, with native forced colors. + +`src/lib/site.ts` is the origin/identity configuration. `Base` provides canonical, +Open Graph, Twitter and accurate software metadata. All pages use the real 1200×630 +brand composition, absolute production image URLs, dimensions, image alt text and +`summary_large_image` cards. Sitemap and llms endpoints derive docs +links from the same collections. `robots.txt` points only to the official domain. +Preview URLs never become canonicals. `public/_headers` supplies basic security and +cache headers; hashed Astro assets are immutable, Pagefind revalidates to prevent +mixing deployment indexes. No tracking scripts or cookie banner are needed. + +## Cloudflare Pages: manual publishing only + +The Git-connected Pages project **nmsh** is prepared in account +`c1644ccedec713ad6e4f2d34cd76aa06`. Its assigned hostname is +`nmsh-5mc.pages.dev`. It has **zero deployments** at setup. The intended custom domain +`nmsh.raisecaterror.com` is not attached and no DNS record has been created. + +| Setting | Value | +| --- | --- | +| Root directory | `website` | +| Framework preset | Astro (static, no adapter/Workers runtime) | +| Build command | `npm ci && npm run build:launch` | +| Build output directory | `dist` (relative to `website`) | +| Environment | `NODE_VERSION=22.22.0`, `SKIP_DEPENDENCY_INSTALL=1` | +| Production branch | `master`, for deployment classification only | +| Git automatic deployments | `deployments_enabled=false` | +| Production automatic deployments | `production_deployments_enabled=false` | +| Preview branch builds | `preview_deployment_setting=none` | +| PR deployment comments | Disabled | + +These controls apply to **every branch**, including `master`. Git remains connected +for source ownership, not deployment triggers. Do not use Save and Deploy, build +hooks, retries or automatic preview builds. `.github/workflows/website.yml` only +checks the static site; it cannot publish it. The old `deploy.yml` GitHub Pages +workflow was disabled through GitHub and must remain disabled. Its existing public +site was not deleted or changed. + +### Credentials and approval + +GitHub environment **nmsh-production** permits only the `master` branch, requires +approval by **raiseCatError**, and disables administrator bypass. Self-review is +allowed for this independent, single-maintainer project; clicking approval is still +required. No deployment has been dispatched. + +`CLOUDFLARE_ACCOUNT_ID` is already an environment variable. The maintainer has added +`CLOUDFLARE_API_TOKEN` as an environment secret; its presence was verified without +reading its value or testing a deployment. The token must have only +**Account / Cloudflare Pages / Edit**, restricted to this account. Never paste it +into chat, commit it, or reuse broad MCP/OAuth +credentials. Pages token access is account-scoped, so do not describe it as isolated +to this one project. Grant no DNS, Workers, R2 or unrelated permissions. Set an expiry +and rotate it when needed. No additional GitHub PAT is required. + +The deploy job receives the token only in its final step, after approval and release +revalidation. Other jobs get a read-only GitHub token. Actions are pinned to immutable +commits; Wrangler is version-pinned in this website's independent lockfile. + +### Publish when ready + +1. Complete `LAUNCH.md`, verify the real v0.20.0 release/tag, synchronize the published + source, then run `npm run verify` and `npm run build:launch`. The current candidate + deliberately fails publication. Do not bypass this gate. +2. Obtain approval to integrate the website and deployment workflow through the normal + `dev` to `master` process. Nothing in this task merges branches. GitHub only exposes + a new manual workflow after it exists on the default branch. +3. In Actions, select **Publish NMSh website**, choose **Run workflow** on `master`, + and enter `nmsh.raisecaterror.com`. Equivalent command, **only when authorized**: + `gh workflow run website-deploy.yml --repo raiseCatError/notMyShell --ref master -f confirmation=nmsh.raisecaterror.com`. +4. The unprivileged build validates release provenance and uploads an immutable static + artifact. Review its exact commit and approve the pending **nmsh-production** job. + It rechecks the public release and manual-only Cloudflare settings, then uploads + that artifact with `wrangler pages deploy`, not a rebuild or arbitrary branch. +5. Check the assigned Pages hostname. Domain attachment/DNS needs separate approval, + as described below. A workflow dispatch is real publication, not a preview test. + +Ordinary source pushes and pull requests never publish. CI-classification tests keep +website and deployment-only changes out of the expensive application jobs. Canonical +source changes reach this site only through a reviewed source synchronization. + +### Custom domain + +After an approved production upload and separate domain approval, associate +**nmsh.raisecaterror.com** in the Pages project's Custom domains section, then follow +Cloudflare's DNS instructions for the existing `raisecaterror.com` zone. Do not add +the domain or a CNAME now. Confirm TLS, HTTP-to-HTTPS behavior, canonical origin, +custom 404, headers and Pagefind on the final host. `/docs/` belongs to this same +project, not a docs subdomain. + +### Cross-site link activation, after the domain works + +The NMSh footer links to the already-live creator homepage. Outgoing launch links +remain staged to avoid sending visitors to an unavailable NMSh site: + +- Only after both `https://nmsh.raisecaterror.com/` and `/docs/` return the actual + expected pages over valid HTTPS, add separate Website and Documentation links to + the root README's Documentation section in an ordinary reviewed commit, keeping the + existing source links. +- Set About → Website to `https://nmsh.raisecaterror.com/` using repository settings + or `gh repo edit raiseCatError/notMyShell --homepage https://nmsh.raisecaterror.com/`. +- The private `raisecaterror-website` repository has a local T7 branch + `feature/nmsh-launch-links`. It stages **View website**, **Read documentation**, + **View repository**, in that order, with a muted repository action. Do not push it + before the NMSh domain works: its current Git integration automatically deploys + preview branches. Its live branch and deployment settings remain unchanged. + +GitHub's public Website field and default README remain unchanged during preparation. +After activation, verify both URLs, redirects and their distinct canonicals before +publishing the homepage link commit. No publishing command in this section has run. + +References: [disabled Git builds](https://developers.cloudflare.com/pages/configuration/git-integration/#disable-automatic-deployments), +[Wrangler uploads and token permissions](https://developers.cloudflare.com/pages/how-to/use-direct-upload-with-continuous-integration/), +[custom domains](https://developers.cloudflare.com/pages/configuration/custom-domains/). + +## Troubleshooting the site + +- **T7 check refused:** stop local disposable work; verify the mounted pinned volume. + Sandbox-restricted disk inspection can require an approved outside-sandbox check. +- **Astro requires a newer Node:** use 22.22.0+ for the website; do not confuse this + with the application's documented 22+ requirement. +- **Search unavailable in dev:** build and preview; the complete docs map works + without JavaScript or an index. A blocked/failed index shows an honest fallback. +- **Generated-source hash mismatch:** revert only your accidental snapshot edit or + run an authorized upstream sync; never bypass provenance validation. +- **Broken local link/anchor:** fix the source link or transform; rerun build. References + outside the selected set should point to their pinned GitHub source, not a local 404. +- **Browser tests cannot launch Chrome:** install Chrome or deliberately configure + another Playwright browser on external storage. No hidden browser download is required. +- **Copy/storage permission denied:** the site gives manual-copy feedback and keeps + theme switching usable without falsely claiming persistence. +- **Cloudflare root install builds the CLI:** set Root directory to `website` and + verify the explicit build command and dependency-install environment above. diff --git a/website/REVIEW.md b/website/REVIEW.md new file mode 100644 index 00000000..d6686174 --- /dev/null +++ b/website/REVIEW.md @@ -0,0 +1,179 @@ +# Website delivery review + +## Refinement pass (2026-10-18) + +This record supersedes the initial delivery's version strategy and measurements below. +The site now targets cumulative v0.20 candidate `15d776ee`; actual published release +history remains separate. `LAUNCH.md` records feature evidence, exact branch ancestry, +the integrated Reactive Horizon evidence, newer follow-up boundary and publication checklist. +No v0.20 release date, published tag or Homebrew availability is fabricated. + +Changes: stronger “A richer way to use the terminal” hook, concrete supporting copy, +Vespyr-only header signature, native tab icons and one authorized 1200×630 social +composition. Website-owned prose has an automated em-dash/stock-language guard; +canonical snapshots and real historical release notes retain their original wording. +The handbook adds practical Setup and execution guides, expands copy/report/compare, +agent/workspace/module coverage, and refreshes canonical references from the candidate. + +The docs bug was reproduced: a sidebar at 631px reset to zero after native navigation, +leaving its active item out of view. Per-tab sidebar-only restoration fixes it without +overriding article scrolling, history or heading anchors. Current-item clicks no longer +reload the article. Thin scrollbar styling was removed; native scrolling stays intact. + +Search now has an aligned icon/label/key hint, a sharp top-anchored dialog, full-row +result links and coordinate-based outside dismissal. Long results grow downward rather +than repositioning the input. A reviewer-identified viewport-height issue was reproduced +by a failing browser test (728px bottom in a 720px viewport), then corrected by deriving +height from the actual top offset and bottom clearance. Keyboard and focus behavior +remain native. Pagefind's relaxed matching found the short `^Z` token for the old +nonsense fixture after the canonical changelog was indexed; a quoted exact nonexistent +term now tests the real no-results path without changing the search engine. + +Motion is limited to a short six-pixel hero entry, short native dialog transitions and +hover feedback. No scroll reveals, hidden waiting content or documentation animation. +Footer personality is confined to one human sentence and “Also, meows.” Existing +editorial layouts, theme palette, static architecture and all eight media placeholders +are deliberately preserved. No NMSh application or capture tooling was launched. + +Current verification includes Node/content/brand/launch tests, Astro diagnostics, +production build, internal links/anchors, metadata/assets, Pagefind and Chrome tests. +The complete `npm run verify` gate passed on 2026-10-18 after the review fixes, with +no Astro errors, warnings or hints. The build validates every HTML page and internal +link/anchor; shared JavaScript is 2,171 bytes gzip, with Pagefind separate. +An additional Git-object audit verifies curated/canonical source paths at +the pinned candidate. `npm run check:upstream` passes; `npm run build:launch` exits +nonzero with the intended unpublished-candidate refusal, not a site build failure. +Rendered website-only QA was inspected at desktop, tablet and mobile sizes, in both +themes, including the hero, long docs, search and footer. Full-size social artwork and +its 400×210 thumbnail were visually inspected. Transparent icon dimensions/alpha and +real local production URLs are checked; human review of actual OS tab presentation, +Safari/Firefox and assistive technology remains appropriate. + +Independent read-only review identified the modal-height issue, a hardcoded candidate +label in tests and stale delivery notes. All are addressed; browser expectations now +derive candidate/published behavior from launch data. No other Critical/Important +finding was reported. Website-only verification does not establish physical NMSh QA. + +The production launch command intentionally refuses candidate metadata. Freshness +checking agrees on the pinned public candidate, published v0.18.0 and Homebrew v0.17.0. +Publication still requires the actual final tag, integrated onboarding/default review, +release acceptance, tap verification, human visual review and separate deployment +approval. The feature worktree is retained; no merge, release or deployment occurs. + +The initial refinement checkpoint `b580d42c` passed local verification and GitHub +Website CI. A final read-only source check then found newly public onboarding +implementation at `15d776ee`, beyond the earlier evidence audit. The website was +repinned as one unified candidate, the real starting-point controls and transition +were documented, and fresh versus legacy queue-summary defaults were corrected. +The remaining newer local-only follow-ups are explicitly listed in `LAUNCH.md`. + +## Initial delivery (2026-10-17, historical) + +The measurements and stable-source decisions below describe the earlier implementation, +not the revised v0.20 baseline. They are retained as historical verification evidence. + +## Scope and isolation + +- Dedicated worktree: `/Volumes/T7Tmp/ai-dev/worktrees/notMyShell/nmsh-website`. +- Branch: `feature/nmsh-website`; base: `origin/dev` at `4a02520d`. +- Content/design foundation: `d97e878f`; working product/docs implementation: `365cbf89`. +- Remaining integration/review-fix commits follow those checkpoints; use `git log` + on this branch for exact final IDs rather than a self-referencing document hash. +- Only `website/` and focused `.github/workflows/` integration changed. Root package, + lockfile, runtime, PTY, prompt, onboarding, existing assets and canonical docs are + unchanged. The main checkout's two pre-existing package edits remain untouched. +- Existing worktrees and the unused `nmsh-site` repository are untouched. This + website worktree remains available for review; no merge or deployment authorized. + +## Shipped for review + +Home, Features, Install, documentation index, planned Learn, Roadmap, Releases, +historical release-note pages, Architecture and custom 404. The handbook includes +18 curated guides and 15 version-pinned technical references, with desktop sidebar, +compact navigation/contents, anchor links, previous/next and local full-text search. +All eight configured product/concept media slots are deliberately pending. + +Astro static HTML; system fonts; a shared dark/light token system; no hydration +framework, third-party font, tracking, runtime API or app compilation. Shared site +JavaScript measured 2058 bytes gzip; Pagefind loads separately when used. The static +build currently produces 52 HTML pages. No Core Web Vitals field measurements or +numerical Lighthouse score is claimed. + +## Verification evidence + +Run from `website/` on verified T7 through `rtk t7run`: + +| Gate | Result | +| --- | --- | +| `npm test` | PASS — content, release drift, source ownership/membership, Markdown pipeline and real CI classification | +| `npm run check` | PASS — no Astro/TypeScript errors, warnings or hints | +| `npm run build` | PASS — provenance, all internal links/anchors, metadata, sitemap, search and bundle budget | +| `npm run test:browser` | PASS — Chrome interaction, responsive layout, keyboard, reduced motion and automated accessibility | +| `npm run verify` | PASS — combined website gate after independent-review fixes | +| `npm run check:upstream` | PASS — release/tag/tap and public release notes/history agree | +| Workflow YAML parsing | PASS — both touched workflows parse | +| Source-object link audit | PASS — rendered pinned GitHub source paths exist in immutable local Git objects | +| Browser runtime/navigation smoke | PASS — product routes return success, no runtime page errors or horizontal overflow | +| `git diff --check` | PASS at verification checkpoints | + +Chrome covers 1440px desktop, 834px tablet and 390px mobile; dark and light themes; +blocked storage writes; clipboard success/denial; search results, no-results and +failed-index fallback; Escape focus return; no-JS docs; compact contents/permalinks; +404 and pending-media behavior. Automated axe checks exercise representative product, +installation, learning and technical-doc pages in both themes. + +Rendered **webpage-only** viewport QA was visually inspected at desktop/tablet/mobile +sizes, using disposable external scratch. These images contain the website and its +honest placeholders; they are not terminal product captures or deliverable media. +No NMSh app, VHS, demo script, terminal recording, GIF/video production or capture +pipeline was used. No physical terminal or human assistive-technology acceptance +is claimed. The application test suite was not run: its runtime is unchanged and +the task explicitly requires an independently verified website pipeline. + +## Independent review and rulings + +One read-only specialist reviewed the implementation and CI integration. No Critical +finding. Its Important release-refresh finding is fixed: check mode now detects +published body/title/date/URL and historical-set drift without writing, and build +validation verifies generated release-note hashes. Regression tests failed before +the change and pass afterward; the complete website gate is green. + +The reviewer called orphan references Minor. I treated the effect as Important: +a removed source could otherwise remain published under a newer release label. +Exact collection membership is now validated, and explicit refresh prunes only +previously generated files whose ownership hash still matches. Unknown/edited files +fail closed. Regression coverage checks extra/missing files and ownership mismatch. +There are no intentionally deferred review findings. + +Other explicit decisions: +- Keep the required `dev` base despite its older application snapshot. Pin website + references to verified stable instead of merging unrelated release/onboarding work. + Tradeoff: refreshing source snapshots is an explicit reviewed maintenance step. +- Keep current Homebrew truth visible: latest stable is v0.18.0 but the verified tap + still references v0.17.0. Do not fix the tap as unrelated work. +- Use the official Astro unified processor for stable Markdown transforms and + foreground `--ignore-lock` servers to avoid agent-inferred orphan preview daemons. +- No live release lookup on page requests or builds. External outages do not make + an existing reviewed site unbuildable; freshness requires the check-only command. + +The reviewer set aside physical-terminal behavior, fresh upstream status, deployed +Cloudflare behavior, Safari/Firefox and human visual/accessibility acceptance. The +primary verified upstream separately. The other exclusions remain honest limits: +no changed app behavior to accept, no production deployment, Chrome-only browser +execution, and human review still required. None is silently represented as passing. + +## Remaining human steps + +1. Review the website visually in the retained worktree; approve wording, hierarchy, + brand treatment and the deliberate media presentation. Test with the actual + browsers and assistive technology you intend to support; Safari/Firefox and a + human screen-reader audit were not exercised here. +2. Approve integration and an eventual production branch separately. Do not merge + to `master` or create a release merely to host this site. Reconcile the stable + branch's separate legacy `site/` Pages workflow during integration. +3. Only after deployment approval, connect Cloudflare Pages to this repository with + root `website`, explicit clean install/build and output `dist`; configure build + watch paths, preview access/indexing policy, custom domain and DNS/TLS. Exact + settings and authoritative references are in README.md. +4. Supply genuine future assets for the eight media IDs documented in README.md and + `src/data/media.json`. No media creation is needed to review or ship this site. diff --git a/website/astro.config.mjs b/website/astro.config.mjs new file mode 100644 index 00000000..ea7e3240 --- /dev/null +++ b/website/astro.config.mjs @@ -0,0 +1,15 @@ +import { defineConfig } from 'astro/config'; +import { unified, rehypeHeadingIds } from '@astrojs/markdown-remark'; +import { sourceLinks, accessibleMarkup } from './scripts/markdown.mjs'; + +export default defineConfig({ + site: 'https://nmsh.raisecaterror.com', + output: 'static', + trailingSlash: 'always', + compressHTML: true, + devToolbar: { enabled: false }, + markdown: { + shikiConfig: { themes: { light: 'github-light', dark: 'github-dark' }, defaultColor: false }, + processor: unified({ remarkPlugins: [sourceLinks], rehypePlugins: [rehypeHeadingIds, accessibleMarkup] }), + }, +}); diff --git a/website/package-lock.json b/website/package-lock.json new file mode 100644 index 00000000..d96b0713 --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,7817 @@ +{ + "name": "@nmsh/website", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@nmsh/website", + "version": "0.1.0", + "dependencies": { + "@astrojs/markdown-remark": "7.3.3", + "astro": "7.4.0" + }, + "devDependencies": { + "@astrojs/check": "0.9.10", + "@axe-core/playwright": "4.14.0", + "@playwright/test": "1.64.1", + "pagefind": "1.5.2", + "sharp": "0.35.5", + "typescript": "~5.9.3", + "wrangler": "4.151.1", + "yaml": "2.9.1" + }, + "engines": { + "node": ">=22.22.0" + } + }, + "node_modules/@astrojs/astro2tsx": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@astrojs/astro2tsx/-/astro2tsx-0.1.3.tgz", + "integrity": "sha512-GMDC7Eo0myBUA/QLBl0u+OcegJ8LiBhccmNZR34KmprQNP8gQUJlWlOOZWUsxNA2rNS6GuPYcGDyLoPnjmn4/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emnapi/core": "1.11.3", + "@napi-rs/wasm-runtime": "1.2.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/check": { + "version": "0.9.10", + "resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.10.tgz", + "integrity": "sha512-zgx/UQMozdjOa3bOxjgeCFdtpE3c9rRX6xHwa+2QXvy8z8Akifu2AtubHyv/zzC2znO8dl8fFWL4K+Ba9kS8HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@astrojs/language-server": "^2.16.7", + "chokidar": "^4.0.3", + "kleur": "^4.1.5", + "yargs": "^18.0.0" + }, + "bin": { + "astro-check": "bin/astro-check.js" + }, + "peerDependencies": { + "typescript": "^5.0.0 || ^6.0.0" + } + }, + "node_modules/@astrojs/compiler-binding": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding/-/compiler-binding-0.5.2.tgz", + "integrity": "sha512-lEn76+0JcMQ5RDXU0wSMWJg2ZppiZvSULpghC29cfv4o9bhvWFDbQjsCtvjjXN9iVLFQ7fL3wTR2L2UU13mP8g==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@astrojs/compiler-binding-android-arm64": "0.5.2", + "@astrojs/compiler-binding-darwin-arm64": "0.5.2", + "@astrojs/compiler-binding-darwin-x64": "0.5.2", + "@astrojs/compiler-binding-linux-arm64-gnu": "0.5.2", + "@astrojs/compiler-binding-linux-arm64-musl": "0.5.2", + "@astrojs/compiler-binding-linux-x64-gnu": "0.5.2", + "@astrojs/compiler-binding-linux-x64-musl": "0.5.2", + "@astrojs/compiler-binding-wasm32-wasi": "0.5.2", + "@astrojs/compiler-binding-win32-arm64-msvc": "0.5.2", + "@astrojs/compiler-binding-win32-x64-msvc": "0.5.2" + } + }, + "node_modules/@astrojs/compiler-binding-android-arm64": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-android-arm64/-/compiler-binding-android-arm64-0.5.2.tgz", + "integrity": "sha512-kckIxon6Rm/Bxzh9mGPYpRRqUbToUQoHptCmpMmE+Yt0Xumq8CqrKUidyYhrBsrHVO7bxuRzzWUUqvpf+iYceQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-darwin-arm64": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-arm64/-/compiler-binding-darwin-arm64-0.5.2.tgz", + "integrity": "sha512-1ZG5Ar3dD8HtodX9XH1mRmE0qBXZlvjYXSFfpgFwocqfWnvhFuqtGUNseL8wgfImiRMztIjpQqlogQFQQtgiag==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-darwin-x64": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-x64/-/compiler-binding-darwin-x64-0.5.2.tgz", + "integrity": "sha512-+kl35S7hiHJ2cb7eujwEsPFYYuSgE11uEE2Z20lAJ+undpgulMcW+weQqFPOSSZccEKHOiLIY+kQHkb4e4092w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-linux-arm64-gnu": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-gnu/-/compiler-binding-linux-arm64-gnu-0.5.2.tgz", + "integrity": "sha512-BqlR0EHNQLPLwgL9+xogjsAc7zGf0cHho2CPdhv0P1yqbVMwGVaGAqrANP6VYAXCqSC43AF4Czcxc2n+oqdesg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-linux-arm64-musl": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-musl/-/compiler-binding-linux-arm64-musl-0.5.2.tgz", + "integrity": "sha512-GRGyby3AZWw5d6JQzidyxPyvGgHWsSusrpgKyFVpDTKctABF7L3Pd5V1cRJbV36/rcu+Eh292/wL7pF7MkmGkQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-linux-x64-gnu": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-gnu/-/compiler-binding-linux-x64-gnu-0.5.2.tgz", + "integrity": "sha512-9XAqGTMQAtIslMsjK3xdyY6OI9d59OZqcLBB6pvD/DcoR0daYswfrKx2azdDERDY4AqAnWPC7odVM1BmxYv3Vw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-linux-x64-musl": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-musl/-/compiler-binding-linux-x64-musl-0.5.2.tgz", + "integrity": "sha512-jOViewGeTh9bSfS8vK72ECxoy2yvmA+Pd/ZcEvBI5c7+7IcfHwZ5qA4L9fRYuOhSrAifvQ2dV4vm1XAtloaWBw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-wasm32-wasi": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-wasm32-wasi/-/compiler-binding-wasm32-wasi-0.5.2.tgz", + "integrity": "sha512-Z7p64doar1uvR5C9Xs6j1CV9lGlPVQX7OQ2aAtZ/xKC5aGS6GB13nyc2UfQXTQLvZrvVwsyaflTQy6ziY5Bekw==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.3", + "@emnapi/runtime": "1.11.3", + "@napi-rs/wasm-runtime": "^1.2.5" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.5.tgz", + "integrity": "sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, + "node_modules/@astrojs/compiler-binding-win32-arm64-msvc": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-arm64-msvc/-/compiler-binding-win32-arm64-msvc-0.5.2.tgz", + "integrity": "sha512-Tr13bh+ahTWsjXjzctK7pFpWV+dUU4FtBfETlR7gO+ueex0F+RDch7eRr0YfKysaRhk9tWm14lsCJ5uWyFPNHQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-win32-x64-msvc": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-x64-msvc/-/compiler-binding-win32-x64-msvc-0.5.2.tgz", + "integrity": "sha512-SfKkI+jOQYLh6wojPvVPV0lFfQnxisweN9B+qCzRvZp7DHAuGif8Y0WcC5PUgN4jb8VKy57LggQDtggR46Du1g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-rs": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-rs/-/compiler-rs-0.5.2.tgz", + "integrity": "sha512-IvwYLB8t1M86rKO4oPdBi9GP4gzXcgrVlNgocjwos1jeeUuEIV8d9Crn1PvYD/5IKteWOxpIHomf7e8iYlkE+Q==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler-binding": "0.5.2" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.13.0.tgz", + "integrity": "sha512-20jxQqnSmpskcGyad7N5l6Xf1gz0UCa6lNde44bEBdaXGpncEYfAZJ142r2ZgzBaG4dUyzGmhqZWv1INU2eiHQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.5", + "@types/mdast": "^4.0.4", + "picomatch": "^4.0.7", + "retext-smartypants": "^6.2.0", + "shiki": "^4.5.0", + "smol-toml": "^1.9.1", + "unified": "^11.0.5", + "yaml": "^2.9.1" + } + }, + "node_modules/@astrojs/language-server": { + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-2.17.2.tgz", + "integrity": "sha512-WzqCBYo0RtKrkPLPMnTz2+UOQPosaFhoG/PvoSyPIgBU0BKeS6qeaYv7Rscq7P1LxJdoN0VosQF2h2TZTsIGHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@astrojs/astro2tsx": "^0.1.2", + "@astrojs/yaml2ts": "^0.2.4", + "@volar/kit": "~2.4.28", + "@volar/language-core": "~2.4.28", + "@volar/language-server": "~2.4.28", + "@volar/language-service": "~2.4.28", + "muggle-string": "^0.4.1", + "tinyglobby": "^0.2.17", + "volar-service-css": "0.0.71", + "volar-service-emmet": "0.0.71", + "volar-service-html": "0.0.71", + "volar-service-prettier": "0.0.71", + "volar-service-typescript": "0.0.71", + "volar-service-typescript-twoslash-queries": "0.0.71", + "volar-service-yaml": "0.0.71", + "vscode-html-languageservice": "^5.6.2", + "vscode-uri": "^3.2.0" + }, + "bin": { + "astro-ls": "bin/nodeServer.js" + }, + "peerDependencies": { + "prettier": "^3.0.0", + "prettier-plugin-astro": ">=0.11.0" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + } + } + }, + "node_modules/@astrojs/markdown-remark": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.3.3.tgz", + "integrity": "sha512-yC7XCJtRztiR3GiP8e0WwUAGJeKm5goMDDzcZykfbOOpQUDztBWG66fBeIOfl1uVxvhdI0iLzkeaHgqPhaRfUg==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.13.0", + "@astrojs/prism": "4.1.0", + "@mdx-js/mdx": "^3.1.1", + "acorn": "^8.19.0", + "estree-util-visit": "^2.0.0", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-html": "^9.0.5", + "hast-util-to-text": "^4.0.2", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.3", + "source-map": "^0.8.0", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.1.0", + "unist-util-visit-parents": "^6.0.2", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/markdown-satteri": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-satteri/-/markdown-satteri-0.4.4.tgz", + "integrity": "sha512-Byhe9CAiGFJrhMACKpgN/FPf4lcwapqwFQAB7E1sPELTEJINfBIpgnEYOBTXzr//fvSSNhfp/X3AqN4mUV9p+A==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.13.0", + "@astrojs/prism": "4.1.0", + "github-slugger": "^2.0.0", + "satteri": "^0.10.5" + } + }, + "node_modules/@astrojs/prism": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.1.0.tgz", + "integrity": "sha512-zvRaL3MKTAXWfVQrHAlYtocRZWXBM6ruF3Bl7Lc7QgFHLRTfwMKo33KczlkvkXDY+qcytKhAWOT9dwUUYLUxAw==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": ">=22.22.0" + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.4.0.tgz", + "integrity": "sha512-Qt2T6RGtYacyV8OZU3IcyFbsp69s6qIcnWvQvn7/p9MKebPCpEswP5Ji4RhifG3DfeBX7e07Pjj5smkd+Tfp7Q==", + "license": "MIT", + "dependencies": { + "ci-info": "^4.4.0", + "dset": "^3.1.4", + "is-docker": "^4.0.0", + "package-manager-detector": "^1.9.0" + }, + "engines": { + "node": ">=22.22.0" + } + }, + "node_modules/@astrojs/yaml2ts": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@astrojs/yaml2ts/-/yaml2ts-0.2.4.tgz", + "integrity": "sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A==", + "dev": true, + "license": "MIT", + "dependencies": { + "yaml": "^2.8.3" + } + }, + "node_modules/@axe-core/playwright": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.14.0.tgz", + "integrity": "sha512-SGUhG5PIHd2eMdpgBF2PyQLOhX+qyDR2PT4cmoS4Qk2ak+jX/rrSvst9l0PGv5WTHolKpUMvVmoHpr4cZgf1RQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "axe-core": "~4.14.0" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "playwright-core": ">= 1.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bruits/satteri-darwin-arm64": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-arm64/-/satteri-darwin-arm64-0.10.5.tgz", + "integrity": "sha512-27KTVl4TJkVahMy/ohyA7qd4938G5UNneFUz/PsScYfpIhj0IVAS23mpcJXdPF44sa6nva198lmV/cKIb2YPyA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@bruits/satteri-darwin-x64": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-x64/-/satteri-darwin-x64-0.10.5.tgz", + "integrity": "sha512-IjnLe3nKspq6qaeqGgjT7MT8VrTV74yWRlaag7ZdNsI8TDAYZ0iPxMCo+9KQZHUk5EyVB+reBI/PFWL5KuFw9Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@bruits/satteri-linux-arm64-gnu": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-gnu/-/satteri-linux-arm64-gnu-0.10.5.tgz", + "integrity": "sha512-glkYXZCJywjP13v67eAyAMSJdF+ncvEbYvgi/wOtffL9tQ27lr/zsyzUfgs+ovjJ9d8JNQKiXeiArJcX8PJL9w==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@bruits/satteri-linux-arm64-musl": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-musl/-/satteri-linux-arm64-musl-0.10.5.tgz", + "integrity": "sha512-yWdgG1g17Nh2QyGVlFUxGRa3FEFwiMcpZEyMNWkbM3deC94cmVc+/i9OuyFpdKuWo3GkgoCtYVOoxk1uCnCZIA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@bruits/satteri-linux-x64-gnu": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-gnu/-/satteri-linux-x64-gnu-0.10.5.tgz", + "integrity": "sha512-FVaLoPT1fBgGl0J+AYebyyXJYBachGl8Oyyrf1lye4RTqCB4S0Gwkj1uM9RJyThUOvx5VUmAT1CnNh1SFHA+kw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@bruits/satteri-linux-x64-musl": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-musl/-/satteri-linux-x64-musl-0.10.5.tgz", + "integrity": "sha512-EHpVAx2bqW3GINHTKkljtxVfQmVDGWIuwOYOP5YghTj+0PkBa2o8oKPRtQ9Kbsr1Fye8jtUcDjhwj2jMNugZKg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@bruits/satteri-wasm32-wasi": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-wasm32-wasi/-/satteri-wasm32-wasi-0.10.5.tgz", + "integrity": "sha512-ypz8c/Zmipxp4IoeDa228Gstv6TLzVmNs3yC6wKCoNSOjx1iwpgzu87Y3hTkXFdwChVGU85qeUDuOIarGUZQLw==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.2.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@bruits/satteri-win32-arm64-msvc": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-arm64-msvc/-/satteri-win32-arm64-msvc-0.10.5.tgz", + "integrity": "sha512-siTV88nb0LRqNpkL2gXboqCwVdq95sLtzMHS1/3eONV2gLbB3NAK46wmSMvCO/yquBvI2lvaFIfd8P12ecsxBw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@bruits/satteri-win32-x64-msvc": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-x64-msvc/-/satteri-win32-x64-msvc-0.10.5.tgz", + "integrity": "sha512-C3IfPvfvMXmlzBxaMPKFS1XiuV9pu2mC7YqkPk7PSvTgPZ8gbdASIpHpztDLvTTQjqZ0z1Ol8tK5X+V6XXC0wQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@capsizecss/unpack": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.1.tgz", + "integrity": "sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@clack/core": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.5.1.tgz", + "integrity": "sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ==", + "license": "MIT", + "dependencies": { + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@clack/prompts": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.8.1.tgz", + "integrity": "sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ==", + "license": "MIT", + "dependencies": { + "@clack/core": "1.5.1", + "fast-string-width": "^3.0.2", + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.5.0.tgz", + "integrity": "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.2.tgz", + "integrity": "sha512-JBP1+Z7ZSNG/d4mRP+y8VC5dka3tZVMLEZRvS+rzQ4DGV1EoxRFQckcJTTkXbHSQiTj0DtNI01Zwb/V2fX0mvQ==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.24", + "workerd": ">1.20260305.0 <2.0.0-0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20261012.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20261012.1.tgz", + "integrity": "sha512-dkTL9aY3V4D40b+jBpGPCistWUPFQ/M61wlWi6i1o8MvHFFgEw7EQkcaYEv6Yd3bkPDD/3TFT4hRsMb2xVZSwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20261012.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20261012.1.tgz", + "integrity": "sha512-2/nVGcKXjNNI+DhxKSepTKvFng9H7aN2WXv6Zu67iqgI6JrxypNt5kTPv4ZHosxrcfyKMci/2RfN7GIrN0BitQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20261012.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20261012.1.tgz", + "integrity": "sha512-uMe7ONHnLxStD1Ye0YFHBO36uj4cStv5P53rW+5GVWXFT8BRjqVixlH0IqCAHe1Pmcwyd1+7fVoGde89MZSCJg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20261012.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20261012.1.tgz", + "integrity": "sha512-I0VMdcXk7ygG5vYDce0ePTBFdnBXC0i9WiBY5mWiv0iTinbfea6/rHqSQ7B1UMpjgrYVs9E0m4bZ9ufsk5GCFA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20261012.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20261012.1.tgz", + "integrity": "sha512-c5hQvNx574UEoknaCFvMa/NdLrrYiALNbe6sx1okW8uOiRYWxrFuA8WpXFnBJ5XJypm0TEAHYaUTGv3rRLpY7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@emmetio/abbreviation": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz", + "integrity": "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emmetio/scanner": "^1.0.4" + } + }, + "node_modules/@emmetio/css-abbreviation": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz", + "integrity": "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emmetio/scanner": "^1.0.4" + } + }, + "node_modules/@emmetio/css-parser": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@emmetio/css-parser/-/css-parser-0.4.1.tgz", + "integrity": "sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emmetio/stream-reader": "^2.2.0", + "@emmetio/stream-reader-utils": "^0.1.0" + } + }, + "node_modules/@emmetio/html-matcher": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@emmetio/html-matcher/-/html-matcher-1.3.0.tgz", + "integrity": "sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@emmetio/scanner": "^1.0.0" + } + }, + "node_modules/@emmetio/scanner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emmetio/scanner/-/scanner-1.0.4.tgz", + "integrity": "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emmetio/stream-reader": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@emmetio/stream-reader/-/stream-reader-2.2.0.tgz", + "integrity": "sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emmetio/stream-reader-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@emmetio/stream-reader-utils/-/stream-reader-utils-0.1.0.tgz", + "integrity": "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emnapi/core": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz", + "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@emnapi/wasi-threads": "1.2.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", + "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "acorn": "^8.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/mdx/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.4.tgz", + "integrity": "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT" + }, + "node_modules/@oxc-project/types": { + "version": "0.154.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.154.0.tgz", + "integrity": "sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.5.2.tgz", + "integrity": "sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.5.2.tgz", + "integrity": "sha512-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74/UzEZy5uA2LD8Nz1AiwKr72vrkZQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/freebsd-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.5.2.tgz", + "integrity": "sha512-7EVzo9+0w+2cbe671BtMj10UlNo83I+HrLVLfRxO731svHRJKUfJ/mo05gU14pe9PCfpKNQT8FS3Xc/oDN6pOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.5.2.tgz", + "integrity": "sha512-Ovt9+K35sqzn8H3ZMXGwls4TD/wMJuvRtShHIsmUQREmaxjrDEX7gHckRCrwYJ4XE1H1p6HkLz3wukrAnsfXQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.5.2.tgz", + "integrity": "sha512-V+tFqHKXhQKq/WqPBD67AFy7scn1/aZID00ws4fSDd+1daSi5UHR9VVlRrOUYKxn3VuFQYRD7lYXdZK1WED1YA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-arm64/-/windows-arm64-1.5.2.tgz", + "integrity": "sha512-hN9Nh90fNW61nNRCW9ZyQrAj/mD0eRvmJ8NlTUzkbuW8kIzGJUi3cxjFkEcMZ5h/8FsKWD/VcouZl4yo1F7B6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.5.2.tgz", + "integrity": "sha512-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F22ol6NJ9IGzvUdmeyZypLW5dyhqsg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@playwright/test": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.64.1.tgz", + "integrity": "sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.64.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@poppinss/colors": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^4.1.5" + } + }, + "node_modules/@poppinss/dumper": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" + } + }, + "node_modules/@poppinss/exception": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", + "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.14.tgz", + "integrity": "sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.14.tgz", + "integrity": "sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.14.tgz", + "integrity": "sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.14.tgz", + "integrity": "sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.14.tgz", + "integrity": "sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.14.tgz", + "integrity": "sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.14.tgz", + "integrity": "sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.14.tgz", + "integrity": "sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.14.tgz", + "integrity": "sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.14.tgz", + "integrity": "sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.14.tgz", + "integrity": "sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.14.tgz", + "integrity": "sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.14.tgz", + "integrity": "sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.14.tgz", + "integrity": "sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.14.tgz", + "integrity": "sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "license": "MIT" + }, + "node_modules/@shikijs/core": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.5.0.tgz", + "integrity": "sha512-6CIgwq+bt2JZ3OSjb/nBkkwEf/CfpHcbam+Mg5Pllco7HAGRQ2/EFhZrrJtbyNTgKwknfVS+llsXGygiDGwJWw==", + "license": "MIT", + "dependencies": { + "@shikijs/primitive": "4.5.0", + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5", + "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.5.0.tgz", + "integrity": "sha512-Ed6UdEj84LyYkwnxM5CvSMWAD+id9ExTYri0/+x41U9zrgNifPSS9lZm0JqBP0VfYI8yr8tY5nbNq9+p9R0kFA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.6" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.5.0.tgz", + "integrity": "sha512-dKVmbJecVdB9recl8a/CU4jdpNXyjJQrIcesIkpS70s5HS02ohpnCSpMUbotPnEUzbAcLQ+S4hwZYbkBBiTcJw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/langs": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.5.0.tgz", + "integrity": "sha512-wwBTqwduXSz0X4v9Qa2EWeFaYJEw1clCncWjoRNQRbVDu3SPRTRWucnouYapKj3b8Q+sk/41Vbc4B5c8nuYzLA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/primitive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.5.0.tgz", + "integrity": "sha512-UM7ShlA8oz0OWxLLO8BmO9Ip6fVqdkt8yF0EH7nOCqHfrx1eEAzF0ZUVK7L+I8NEgeu1PEWSRlxaiPPeCb4iDg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/themes": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.5.0.tgz", + "integrity": "sha512-zGb87H3UBbpuI4yoh5UguB1XbU+2BDU3kflM7UfZ+Cy2hWo9lBtqjSVK20gmLY6ukYl040hAypGXMgud9vul8Q==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/types": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.5.0.tgz", + "integrity": "sha512-drtPrtD8N0DENyD2cyt6Ajy+IWsiCLgMxODwLTpvH1UsN6a9jf90tNTPjqwHvA+0Lzu9e0txk8DjfqH1p9UJRw==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@speed-highlight/core": { + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.24.tgz", + "integrity": "sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz", + "integrity": "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", + "license": "ISC" + }, + "node_modules/@volar/kit": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/kit/-/kit-2.4.28.tgz", + "integrity": "sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-service": "2.4.28", + "@volar/typescript": "2.4.28", + "typesafe-path": "^0.2.2", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/language-server": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.28.tgz", + "integrity": "sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "@volar/language-service": "2.4.28", + "@volar/typescript": "2.4.28", + "path-browserify": "^1.0.1", + "request-light": "^0.7.0", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@volar/language-service": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.28.tgz", + "integrity": "sha512-Rh/wYCZJrI5vCwMk9xyw/Z+MsWxlJY1rmMZPsxUoJKfzIRjS/NF1NmnuEcrMbEVGja00aVpCsInJfixQTMdvLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vscode/emmet-helper": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz", + "integrity": "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "emmet": "^2.4.3", + "jsonc-parser": "^2.3.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.15.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vscode/emmet-helper/node_modules/jsonc-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", + "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vscode/l10n": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz", + "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-i18n": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ajv-i18n/-/ajv-i18n-4.2.0.tgz", + "integrity": "sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^8.0.0-beta.0" + } + }, + "node_modules/am-i-vibing": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/am-i-vibing/-/am-i-vibing-0.5.0.tgz", + "integrity": "sha512-KLcsDhqf1XRrJmJr+9I/C0EX2K+sGrAGZFgkmd84c8TJO9zJBu+/kFInjazu1KfJWFhWDKdRWkpBzEs7s35DVA==", + "license": "MIT", + "dependencies": { + "process-ancestry": "^0.1.0" + }, + "bin": { + "am-i-vibing": "dist/cli.mjs" + } + }, + "node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/astro": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/astro/-/astro-7.4.0.tgz", + "integrity": "sha512-6ThZU2bp7asGSceVcnh3r23dE88HpYN5vxtnCWYYrjr8lpQjx4eDW6/W8WgNlcc+T/b8zHmQ/EcS4DpQG4ZoOg==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler-rs": "^0.5.1", + "@astrojs/internal-helpers": "0.13.0", + "@astrojs/markdown-satteri": "0.4.4", + "@astrojs/telemetry": "3.4.0", + "@capsizecss/unpack": "^4.0.1", + "@clack/prompts": "^1.8.1", + "@oslojs/encoding": "^1.1.0", + "am-i-vibing": "^0.5.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "ci-info": "^4.4.0", + "clsx": "^2.1.1", + "common-ancestor-path": "^2.0.0", + "cookie": "^2.0.1", + "devalue": "^6.0.2", + "diff": "^9.0.0", + "dset": "^3.1.4", + "es-module-lexer": "^3.0.3", + "flattie": "^1.1.1", + "fontace": "~0.4.1", + "get-tsconfig": "5.0.0-beta.6", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.3.0", + "jsonc-parser": "^3.3.1", + "magic-string": "^1.4.3", + "magicast": "^0.5.5", + "mrmime": "^2.0.1", + "neotraverse": "^1.0.1", + "obug": "^3.0.0", + "p-limit": "^7.3.3", + "p-queue": "^9.3.5", + "package-manager-detector": "^1.9.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.7", + "rolldown": "^1.2.13", + "shiki": "^4.5.0", + "smol-toml": "^1.9.1", + "svgo": "^4.1.0", + "tinyclip": "^1.0.3", + "tinyexec": "^1.3.1", + "tinyglobby": "^0.2.17", + "tinyps": "^0.5.0", + "ultrahtml": "^1.7.0", + "unifont": "^1.0.3", + "unstorage": "^1.17.5", + "verkit": "^0.5.0", + "vite": "^8.3.4", + "vitefu": "^1.1.3", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^22.0.0", + "zod": "^4.6.6" + }, + "bin": { + "astro": "bin/astro.mjs" + }, + "engines": { + "node": ">=22.22.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.35.4" + }, + "peerDependencies": { + "@astrojs/markdown-remark": "^7.3.0" + }, + "peerDependenciesMeta": { + "@astrojs/markdown-remark": { + "optional": true + } + } + }, + "node_modules/axe-core": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.14.0.tgz", + "integrity": "sha512-9WTZxEjsZ7b13TH8JPmbV2z8CHbl80/2hm3XPEG4JgNdQLK81IBRXmSxHfMAOkSqQeRxT/0dwNDz2GOm3zzpcQ==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/common-ancestor-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/cookie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-2.0.1.tgz", + "integrity": "sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cookie-es": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz", + "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==", + "license": "MIT" + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/css-select": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", + "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^7.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "nth-check": "^2.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", + "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-6.0.3.tgz", + "integrity": "sha512-3woMEITftG1wKeO/4d/QXH1DVKctTSZg3jNGPpjp9hNr5Hos8MCFF5McTML5ZtgKDObm2R591B0spRp2IVernA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/diff": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/emmet": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.4.11.tgz", + "integrity": "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "./packages/scanner", + "./packages/abbreviation", + "./packages/css-abbreviation", + "./" + ], + "dependencies": { + "@emmetio/abbreviation": "^2.3.3", + "@emmetio/css-abbreviation": "^2.1.8" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/es-module-lexer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-3.0.4.tgz", + "integrity": "sha512-BldNY5mwkVu+cQ5Ymn3e6hB9Sx8XHx4ZevWbiUyZbfKxJxYO27Hf0wfzkxIaD5CAJMaGl3YspKELj0ff/7nCMA==", + "license": "MIT" + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.1.tgz", + "integrity": "sha512-B0np3dcdxqILX5e9nEi5/Fr4K7gL4oYFVPV1zRa2e9wRCbQoZZNWOZFYyoInvXUPJXBXjss+QXlWLJChDEHDkA==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/fnv1a-64": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/fnv1a-64/-/fnv1a-64-0.1.2.tgz", + "integrity": "sha512-mJbcILTPybAR1jdOwt/lVv8N2cffeJFFP+gVbSAwLEx1ujXH27RpPd8Rokec/KX9c76UYIB6Co+H4lQzxPnJfA==", + "license": "MIT" + }, + "node_modules/fontace": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", + "integrity": "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.2" + } + }, + "node_modules/fontkitten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz", + "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==", + "license": "MIT", + "dependencies": { + "tiny-inflate": "^1.0.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "5.0.0-beta.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.6.tgz", + "integrity": "sha512-X6fBC0pmImC70gvX2zm56go9hx0MyoGVdG0tUCkg/D+Xnh5TJsOZ7iDbOdI3PvmtrDxnu1YdDufpK2QJX1Meqw==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "engines": { + "node": ">=20.20.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/h3": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz", + "integrity": "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.3", + "crossws": "^0.3.5", + "defu": "^6.1.6", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.4", + "radix3": "^1.1.2", + "ufo": "^1.6.3", + "uncrypto": "^0.1.3" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", + "license": "BSD-2-Clause" + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-4.0.0.tgz", + "integrity": "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.3.tgz", + "integrity": "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "node_modules/magicast": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.5.tgz", + "integrity": "sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "source-map-js": "^1.2.1" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.3.tgz", + "integrity": "sha512-xPgpDNl0/OXHsI7TlaIs22lWnH3KlpvZdXG1ET/m/YT2Hhdkx8lJV4kLphE6l9simyM4KPDFBwas08BgEob6Jw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.1.0.tgz", + "integrity": "sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.1.tgz", + "integrity": "sha512-OuJHqvr455pwu2OaOrir7dbzqs4jlWKOtlu9L6GjcIjjQwNyw02VntQsr/Ek6/A13vgnXfUhBekWRUP1OkNivw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.2.0.tgz", + "integrity": "sha512-2Jn/ADrHFFyyniBMi0bncTbJqU78K3/TEYvzvwrWWzD9MYPUa/Gm4r3nOeKFQg/PDM5BsbDYdc8ackW4NTY6NA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "node_modules/micromark": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.3.tgz", + "integrity": "sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-edit-map": "^1.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.4.tgz", + "integrity": "sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.1.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-edit-map": "^1.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.3" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.2.tgz", + "integrity": "sha512-pRzm4kDTu0MjlmBkxmS9yYhw60nncfcEwu9NNdPFSQEFXS95ZKyIIyTSHu/o3ReBUrLKYEq+7YaXCRn/bPB4MA==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.1.0.tgz", + "integrity": "sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-edit-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-edit-map/-/micromark-util-edit-map-1.0.0.tgz", + "integrity": "sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.3.tgz", + "integrity": "sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/miniflare": { + "version": "5.20261012.1-beta", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20261012.1-beta.tgz", + "integrity": "sha512-8Canhy6g9+vAhqMc0o80YphqYIwpiA3aAp8aX+uDXU7J/qbUeoZzcII9c2V1P4uT/eHD4SB93qet1o1bRY3BuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "sharp": "0.35.5", + "undici": "7.29.1", + "workerd": "1.20261012.1", + "ws": "8.21.0", + "youch": "4.1.0-beta.10" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/miniflare/node_modules/undici": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.1.tgz", + "integrity": "sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neotraverse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-1.0.1.tgz", + "integrity": "sha512-WmmLty1YWwJl9yZi77v2dVIV6X2kuYV8YYBI/G3LWGKdGHmHUvL1z7FW0iDvEvGAwNEoc5x1tOOOyDnf5jJw/w==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-mock-http": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.5.tgz", + "integrity": "sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/obug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz", + "integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==", + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/oniguruma-parser": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.2", + "regex": "^6.1.0", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/p-limit": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.3.tgz", + "integrity": "sha512-SKgVvDU5TNIxQ4r30U75BNLC6a+6GOEjngrk/ysM5+Zu3oJuk1SC5ApzIzY/7PjCEjoVPMWJ0zho/4QUiEB7TQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.2.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.5.tgz", + "integrity": "sha512-Lki/zhXFVmDhesBUn/yHRLW9Bte7mE7xoFHzTR8sIomV7a2z+LxYAPkGvdfidNiu4fKSfjtawixxSHcZci5QcQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.2.tgz", + "integrity": "sha512-prbX4Z3YszrFNgH+MW5Zoeq3baXrMtP/MQnFeET90UB/GtGcGDQ5Usg9OCy6ETjTTntOw1SL2z9fMPUppN3Guw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.9.0.tgz", + "integrity": "sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA==", + "license": "MIT" + }, + "node_modules/pagefind": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.5.2.tgz", + "integrity": "sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q==", + "dev": true, + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.5.2", + "@pagefind/darwin-x64": "1.5.2", + "@pagefind/freebsd-x64": "1.5.2", + "@pagefind/linux-arm64": "1.5.2", + "@pagefind/linux-x64": "1.5.2", + "@pagefind/windows-arm64": "1.5.2", + "@pagefind/windows-x64": "1.5.2" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-latin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", + "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "@types/unist": "^3.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0", + "unist-util-visit-children": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.1.tgz", + "integrity": "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.64.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.1.tgz", + "integrity": "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/postcss": { + "version": "8.5.30", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.30.tgz", + "integrity": "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.20", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prettier": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz", + "integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-ancestry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/process-ancestry/-/process-ancestry-0.1.0.tgz", + "integrity": "sha512-tGqJW/UnclpYASFcM6Xh8D8l/BMtaQ9+CSG0vlJSJTcdMM4lDRv4c6H0Pdcsfted+bVczdYSfk2fdukg2gQkZg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", + "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", + "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.3.tgz", + "integrity": "sha512-gCaK+ndZ0hYezlqFegHFCVh2CQemsi0Npdh1qVM9bxlUFknjkbP6VmojWhddOCrbK0PbbacmYLWfTULRiT1eWA==", + "license": "MIT", + "dependencies": { + "retext": "^9.0.0", + "retext-smartypants": "^6.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/request-light": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.7.0.tgz", + "integrity": "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retext": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", + "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "parse-latin": "^7.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", + "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rolldown": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.14.tgz", + "integrity": "sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.154.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.14", + "@rolldown/binding-android-arm64": "1.2.14", + "@rolldown/binding-darwin-arm64": "1.2.14", + "@rolldown/binding-darwin-x64": "1.2.14", + "@rolldown/binding-freebsd-x64": "1.2.14", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.14", + "@rolldown/binding-linux-arm64-gnu": "1.2.14", + "@rolldown/binding-linux-arm64-musl": "1.2.14", + "@rolldown/binding-linux-ppc64-gnu": "1.2.14", + "@rolldown/binding-linux-s390x-gnu": "1.2.14", + "@rolldown/binding-linux-x64-gnu": "1.2.14", + "@rolldown/binding-linux-x64-musl": "1.2.14", + "@rolldown/binding-openharmony-arm64": "1.2.14", + "@rolldown/binding-win32-arm64-msvc": "1.2.14", + "@rolldown/binding-win32-x64-msvc": "1.2.14" + } + }, + "node_modules/satteri": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/satteri/-/satteri-0.10.5.tgz", + "integrity": "sha512-Ao1LKpAEa9Wdg0otgbVKViZHEq9ebdXe4DMrp3s9vQAU0HNIuHnFEuMuOcm0ZIXyV0Yzxj91NvhLpvXZJO/5ZQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.5", + "@types/hast": "^3.0.5", + "@types/mdast": "^4.0.4", + "@types/unist": "^3.0.3" + }, + "optionalDependencies": { + "@bruits/satteri-darwin-arm64": "0.10.5", + "@bruits/satteri-darwin-x64": "0.10.5", + "@bruits/satteri-linux-arm64-gnu": "0.10.5", + "@bruits/satteri-linux-arm64-musl": "0.10.5", + "@bruits/satteri-linux-x64-gnu": "0.10.5", + "@bruits/satteri-linux-x64-musl": "0.10.5", + "@bruits/satteri-wasm32-wasi": "0.10.5", + "@bruits/satteri-win32-arm64-msvc": "0.10.5", + "@bruits/satteri-win32-x64-msvc": "0.10.5" + } + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/shiki": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.5.0.tgz", + "integrity": "sha512-OcRW72ngQvc+MSVWBRIovC/BTW589iUE/Vxav7VeM4RcY9MUjqB+IR6ILG1k9nuW6LiG/nct9uma4/MBtnZbmQ==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "4.5.0", + "@shikijs/engine-javascript": "4.5.0", + "@shikijs/engine-oniguruma": "4.5.0", + "@shikijs/langs": "4.5.0", + "@shikijs/themes": "4.5.0", + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/smol-toml": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.1.tgz", + "integrity": "sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/source-map": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0.tgz", + "integrity": "sha512-d8EqvL+k/SOXCreS/SUzg2ciyHqBBLcN/yuRjFsbvVhHTE2pgei7oAhmPM7kWFbkX6OSMQfUq4KbkF3au9lhYQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/std-env": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz", + "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/svgo": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.1.0.tgz", + "integrity": "sha512-bkxnTg1kSU0guhIBmibA6UUhrQmPVA1XsQLN+ylCd+UWzbnLkySOcXpyk1mrl05f+pcaCx2eHb+sp6BgMZWX+Q==", + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^6.0.0", + "css-tree": "^3.0.1", + "css-what": "^7.0.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "1.6.1" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tinyclip": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-1.0.3.tgz", + "integrity": "sha512-nTv2raKFMzGG6JJp0lpNDsRPdfFE6UHlu2rVEjXo8vkx1ZS3LW2CVXqIaLu8xZlu+6tbFs9V58Qc/Jmk/Ii7wA==", + "license": "MIT", + "engines": { + "node": "^16.14.0 || >= 17.3.0" + } + }, + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyps": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/tinyps/-/tinyps-0.5.0.tgz", + "integrity": "sha512-Llu7nEtVOGHh7jok32l6+udlu+4Np5vsYVB7zeTK614coSsro2qjwte7MFt/ouLM/h4kXqbGXktSMMvMsttSDw==", + "license": "MIT" + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "devOptional": true, + "license": "0BSD" + }, + "node_modules/typesafe-path": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/typesafe-path/-/typesafe-path-0.2.2.tgz", + "integrity": "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-auto-import-cache": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/typescript-auto-import-cache/-/typescript-auto-import-cache-0.3.6.tgz", + "integrity": "sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.8" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/ultrahtml": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.7.0.tgz", + "integrity": "sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g==", + "license": "MIT" + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, + "node_modules/undici": { + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.12.2.tgz", + "integrity": "sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==", + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/unenv": { + "version": "2.0.0-rc.24", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unifont": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-1.0.3.tgz", + "integrity": "sha512-XGnEwj5sWdCbiBX01mWLSBncS0yMRjnLygau3IfdOlfjMacNIQvTGW4VH7pWBq+gGWArfSef/CensMLjMmIr/A==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.1.0", + "fnv1a-64": "^0.1.2", + "std-env": "^4.2.0", + "undici": "^8.0.0" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", + "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", + "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unstorage": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.6.tgz", + "integrity": "sha512-gjKtcmn/KwZuJZO9o2sc0esAQnhmTw0LviDHwevTXbJMYs0dRTp8ZEEWXHU0vTpzbN3cA0yxlTeyXssMp3zDqw==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.11", + "lru-cache": "^11.5.3", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.4" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/unstorage/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/unstorage/node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/verkit": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/verkit/-/verkit-0.5.0.tgz", + "integrity": "sha512-u4J1hr6Y+C/hit34GwL3eIcUf3wngT8TlYUjK7tk0jxydKLAq3sYvifjdTDA6U3KCKRcQCQDGf/srjxfZF3l7g==", + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz", + "integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==", + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.29", + "rolldown": "~1.2.12", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/volar-service-css": { + "version": "0.0.71", + "resolved": "https://registry.npmjs.org/volar-service-css/-/volar-service-css-0.0.71.tgz", + "integrity": "sha512-wRRFt9BpjMKCazcgOh67MSjUjiWUCAh99DyYSDIOTuxaRjEtDC7PpB0k1Y1wbJIW/pVtMUSVbpPo3UGSm0Byxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-css-languageservice": "^6.3.0", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-emmet": { + "version": "0.0.71", + "resolved": "https://registry.npmjs.org/volar-service-emmet/-/volar-service-emmet-0.0.71.tgz", + "integrity": "sha512-zqjzt6bN95e3CUstBm0PBFAJnrfz0ZAARka87fart46/gNCLLuP3Vujy8V/J8HEziTFLnfkgIASLFYPUhonJcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emmetio/css-parser": "^0.4.1", + "@emmetio/html-matcher": "^1.3.0", + "@vscode/emmet-helper": "^2.9.3", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-html": { + "version": "0.0.71", + "resolved": "https://registry.npmjs.org/volar-service-html/-/volar-service-html-0.0.71.tgz", + "integrity": "sha512-e8tHPhgQ7ooLfudAEIku+kgd9pWkq3SSz8RbnQDI1+Eb8wbenkLGHqoirLqz5ORLV6wIMr2Iv08RWBG5eOcgpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-html-languageservice": "^5.3.0", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-prettier": { + "version": "0.0.71", + "resolved": "https://registry.npmjs.org/volar-service-prettier/-/volar-service-prettier-0.0.71.tgz", + "integrity": "sha512-Rz7JVH3qD108UCdmIEiZvOBNljMt2nLFdbN8AXcDfn7xD9F5I2aCIsDVqBbXw21PsnxG0b7MfwtNF+zPS/NKUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0", + "prettier": "^2.2 || ^3.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + }, + "prettier": { + "optional": true + } + } + }, + "node_modules/volar-service-typescript": { + "version": "0.0.71", + "resolved": "https://registry.npmjs.org/volar-service-typescript/-/volar-service-typescript-0.0.71.tgz", + "integrity": "sha512-yTtM/BVT6hoyEYnDtaCyAtNhdNeS/mhTTABlBOdw3NNiRBUin3IznFJpgfjer4c6RYopiPjjQjc9VFhxVl1mLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-browserify": "^1.0.1", + "semver": "^7.6.2", + "typescript-auto-import-cache": "^0.3.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-nls": "^5.2.0", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-typescript-twoslash-queries": { + "version": "0.0.71", + "resolved": "https://registry.npmjs.org/volar-service-typescript-twoslash-queries/-/volar-service-typescript-twoslash-queries-0.0.71.tgz", + "integrity": "sha512-9K2k72s4n7rV9s4bX0MyjbX9iBribvKZbBJKuEmTCZfeWJXs6Yh7bGpY4eoc7UufAjvpheBqwyZCOIPBvxCv0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-yaml": { + "version": "0.0.71", + "resolved": "https://registry.npmjs.org/volar-service-yaml/-/volar-service-yaml-0.0.71.tgz", + "integrity": "sha512-qYGWGuVpUTnZGu5P/CR4KLK4aIR8RrcVnmfZ2eRcj9q/I8VZCoC5yy9FtEvfNvnDp4MU17yhdJcvpQPIqhJS2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-uri": "^3.0.8", + "yaml-language-server": "~1.23.0" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/vscode-css-languageservice": { + "version": "6.3.10", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.3.10.tgz", + "integrity": "sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vscode/l10n": "^0.0.18", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-languageserver-types": "3.17.5", + "vscode-uri": "^3.1.0" + } + }, + "node_modules/vscode-css-languageservice/node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-html-languageservice": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.6.2.tgz", + "integrity": "sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vscode/l10n": "^0.0.18", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-languageserver-types": "^3.17.5", + "vscode-uri": "^3.1.0" + } + }, + "node_modules/vscode-json-languageservice": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-4.1.8.tgz", + "integrity": "sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsonc-parser": "^3.0.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.16.0", + "vscode-nls": "^5.0.0", + "vscode-uri": "^3.0.2" + }, + "engines": { + "npm": ">=7.0.0" + } + }, + "node_modules/vscode-jsonrpc": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.3.tgz", + "integrity": "sha512-qETYf9PCl1lVfwR+oBmMfc4vP2nqLNA35ABxorw3j06+xlKRgDVxbLgKm4Y/xPL0YWdeDjLoI4ImICIT3nB0zQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.18.4.tgz", + "integrity": "sha512-CCXbyr99dafRJzJH/0Ryk7ly5T8XPmSR0//OuF2w4m+9JiOin7zcAT5iET8GRE7Z9taFBejb3GbM0a0mbjdbrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "9.0.3", + "vscode-languageserver-types": "3.18.4" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.15.tgz", + "integrity": "sha512-Gsfr9S8LU3S6885cS+NaleasJ0n3V5y/SOTqHCifQiXWFw3G9w9/YItDfwHrlCz+dSZWWZ8ilYVMcJe7Sz7Sxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.18.4.tgz", + "integrity": "sha512-nQsacoiijvl0fRf2q28uFDrAVwRhdCa3QveiF7/CX0r18mLR5FU/1WskSsCKOjmEbtteKUnY8x8Yzxop+k0zwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-languageserver/node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver/node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver/node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-nls": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.2.0.tgz", + "integrity": "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.2.0.tgz", + "integrity": "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/workerd": { + "version": "1.20261012.1", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20261012.1.tgz", + "integrity": "sha512-tEHoMPsMga0seVjxfsR8Pd3f8IRqYwkq++dTUKI5Gdgj6zuSDluYwisydq+dn90SsIACwBquJUQ1nBpcOqaWTA==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20261012.1", + "@cloudflare/workerd-darwin-arm64": "1.20261012.1", + "@cloudflare/workerd-linux-64": "1.20261012.1", + "@cloudflare/workerd-linux-arm64": "1.20261012.1", + "@cloudflare/workerd-windows-64": "1.20261012.1" + } + }, + "node_modules/wrangler": { + "version": "4.151.1", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.151.1.tgz", + "integrity": "sha512-OAsa9UAHZshM6mq2PXcVoyxXgWThAanZMBc/IirtqEWd/BauRtmUKzAniOu/pU0pd3pxKuMZ2vsbF4eo6RiNDA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@cloudflare/kv-asset-handler": "0.5.0", + "@cloudflare/unenv-preset": "2.16.2", + "blake3-wasm": "2.1.5", + "esbuild": "0.28.2", + "miniflare": "5.20261012.1-beta", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.24", + "workerd": "1.20261012.1" + }, + "bin": { + "cf-wrangler": "bin/cf-wrangler.js", + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=22.0.0" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^5.20261006.1" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xxhash-wasm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yaml-language-server": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/yaml-language-server/-/yaml-language-server-1.23.0.tgz", + "integrity": "sha512-3qVyCOexLCWw06PQa5kRPwvMWMZ/eZeCRWUvgD6a0OkqL/4iCnxy2WumbWifa937Uo5xhyWJ0uxlU39ljhNh7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vscode/l10n": "^0.0.18", + "ajv": "^8.17.1", + "ajv-draft-04": "^1.0.0", + "ajv-i18n": "^4.2.0", + "prettier": "^3.8.1", + "request-light": "^0.5.7", + "vscode-json-languageservice": "4.1.8", + "vscode-languageserver": "^9.0.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.16.0", + "vscode-uri": "^3.0.2", + "yaml": "2.8.3" + }, + "bin": { + "yaml-language-server": "bin/yaml-language-server" + } + }, + "node_modules/yaml-language-server/node_modules/request-light": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.8.tgz", + "integrity": "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==", + "dev": true, + "license": "MIT" + }, + "node_modules/yaml-language-server/node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.2.0.tgz", + "integrity": "sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^8.2.1", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/youch": { + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" + } + }, + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" + } + }, + "node_modules/youch/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/zod": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 00000000..8a4980f8 --- /dev/null +++ b/website/package.json @@ -0,0 +1,36 @@ +{ + "name": "@nmsh/website", + "version": "0.1.0", + "private": true, + "type": "module", + "engines": { + "node": ">=22.22.0" + }, + "scripts": { + "dev": "astro dev --host 127.0.0.1 --ignore-lock", + "check": "astro check", + "test": "node --test tests/*.test.mjs", + "build": "node scripts/validate-content.mjs && astro build --force && pagefind --site dist && node scripts/check-site.mjs", + "build:launch": "node scripts/validate-content.mjs --require-published && node scripts/sync-upstream.mjs --check && npm run build", + "brand": "node scripts/brand.mjs", + "preview": "astro preview --host 127.0.0.1 --ignore-lock", + "test:browser": "playwright test", + "verify": "npm test && npm run check && npm run build && npm run test:browser", + "sync:upstream": "node scripts/sync-upstream.mjs --write", + "check:upstream": "node scripts/sync-upstream.mjs --check" + }, + "dependencies": { + "@astrojs/markdown-remark": "7.3.3", + "astro": "7.4.0" + }, + "devDependencies": { + "@astrojs/check": "0.9.10", + "@axe-core/playwright": "4.14.0", + "@playwright/test": "1.64.1", + "pagefind": "1.5.2", + "sharp": "0.35.5", + "typescript": "~5.9.3", + "wrangler": "4.151.1", + "yaml": "2.9.1" + } +} diff --git a/website/playwright.config.mjs b/website/playwright.config.mjs new file mode 100644 index 00000000..ab2ab327 --- /dev/null +++ b/website/playwright.config.mjs @@ -0,0 +1,22 @@ +import { defineConfig } from '@playwright/test'; + +const port = process.env.WEBSITE_TEST_PORT || '4321'; +const baseURL = `http://127.0.0.1:${port}`; + +export default defineConfig({ + testDir: './tests/browser', + fullyParallel: false, + workers: 1, + reporter: 'list', + use: { + baseURL, + launchOptions: { channel: 'chrome' }, + trace: 'off', video: 'off', screenshot: 'off', + }, + webServer: { + command: `${process.env.WEBSITE_TEST_DEV ? 'npm run dev' : 'npm run preview'} -- --port ${port}`, + url: baseURL, + reuseExistingServer: false, + timeout: 30000, + }, +}); diff --git a/website/public/_headers b/website/public/_headers new file mode 100644 index 00000000..dbc25c4f --- /dev/null +++ b/website/public/_headers @@ -0,0 +1,11 @@ +/* + X-Content-Type-Options: nosniff + Referrer-Policy: strict-origin-when-cross-origin + Permissions-Policy: camera=(), microphone=(), geolocation=() + X-Frame-Options: DENY +/_astro/* + Cache-Control: public, max-age=31536000, immutable +/pagefind/* + Cache-Control: public, max-age=0, must-revalidate +/brand/* + Cache-Control: public, max-age=86400 diff --git a/website/public/brand/apple-touch-icon.png b/website/public/brand/apple-touch-icon.png new file mode 100644 index 00000000..23f62dd2 Binary files /dev/null and b/website/public/brand/apple-touch-icon.png differ diff --git a/website/public/brand/favicon-16.png b/website/public/brand/favicon-16.png new file mode 100644 index 00000000..79ed1831 Binary files /dev/null and b/website/public/brand/favicon-16.png differ diff --git a/website/public/brand/favicon-32.png b/website/public/brand/favicon-32.png new file mode 100644 index 00000000..5b871194 Binary files /dev/null and b/website/public/brand/favicon-32.png differ diff --git a/website/public/brand/favicon.svg b/website/public/brand/favicon.svg new file mode 100644 index 00000000..a6ff7c21 --- /dev/null +++ b/website/public/brand/favicon.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/public/brand/nmsh-lockup.svg b/website/public/brand/nmsh-lockup.svg new file mode 100644 index 00000000..1964d6b1 --- /dev/null +++ b/website/public/brand/nmsh-lockup.svg @@ -0,0 +1,62 @@ + + Vespyr and notMyShell + The NMSh cat sits to the left of the NMSh and notMyShell wordmark, centered vertically. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/public/brand/nmsh-logo.png b/website/public/brand/nmsh-logo.png new file mode 100644 index 00000000..d84c1908 Binary files /dev/null and b/website/public/brand/nmsh-logo.png differ diff --git a/website/public/brand/social-preview.png b/website/public/brand/social-preview.png new file mode 100644 index 00000000..d8fa3790 Binary files /dev/null and b/website/public/brand/social-preview.png differ diff --git a/website/public/brand/vespyr.svg b/website/public/brand/vespyr.svg new file mode 100644 index 00000000..ecd2f142 --- /dev/null +++ b/website/public/brand/vespyr.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/public/robots.txt b/website/public/robots.txt new file mode 100644 index 00000000..583f1cf6 --- /dev/null +++ b/website/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://nmsh.raisecaterror.com/sitemap.xml diff --git a/website/scripts/brand.mjs b/website/scripts/brand.mjs new file mode 100644 index 00000000..a74edbf5 --- /dev/null +++ b/website/scripts/brand.mjs @@ -0,0 +1,20 @@ +import { readFile, writeFile } from 'node:fs/promises'; +import sharp from 'sharp'; + +const root = new URL('../', import.meta.url); +const brand = new URL('public/brand/', root); +const lockup = await readFile(new URL('nmsh-lockup.svg', brand), 'utf8'); +const sprite = lockup.match(/]*>([\s\S]*?)<\/g>/)?.[1]; +if (!sprite || !sprite.includes('x="416"')) throw new Error('The approved Vespyr sprite could not be located. Review the source artwork.'); +const cat = `${sprite}\n`; +const favicon = `${sprite}\n`; +await writeFile(new URL('vespyr.svg', brand), cat); +await writeFile(new URL('favicon.svg', brand), favicon); +for (const size of [16, 32, 180]) { + const filename = size === 180 ? 'apple-touch-icon.png' : `favicon-${size}.png`; + await sharp(Buffer.from(favicon)).resize(size, size, { kernel: 'nearest' }).png({ compressionLevel: 9 }).toFile(new URL(filename, brand).pathname); +} +const template = await readFile(new URL('src/brand/social-preview.svg', root), 'utf8'); +const artwork = template.replace('href="../../public/brand/vespyr.svg"', `href="data:image/svg+xml;base64,${Buffer.from(cat).toString('base64')}"`); +await sharp(Buffer.from(artwork)).png({ compressionLevel: 9 }).toFile(new URL('social-preview.png', brand).pathname); +console.log('Exported genuine Vespyr icons and the 1200×630 social preview. No product media generated.'); diff --git a/website/scripts/check-site.mjs b/website/scripts/check-site.mjs new file mode 100644 index 00000000..13dfc63a --- /dev/null +++ b/website/scripts/check-site.mjs @@ -0,0 +1,56 @@ +import { readFile, readdir, stat } from 'node:fs/promises'; +import path from 'node:path'; +import { gzipSync } from 'node:zlib'; + +const root = new URL('../dist/', import.meta.url); +const origin = 'https://nmsh.raisecaterror.com'; +async function allFiles(directory) { + const entries = await readdir(directory, { withFileTypes: true }); + const nested = await Promise.all(entries.map(entry => entry.isDirectory() ? allFiles(new URL(`${entry.name}/`, directory)) : new URL(entry.name, directory))); + return nested.flat(); +} +const files = await allFiles(root); +const htmlFiles = files.filter(file => file.pathname.endsWith('.html')); +const pages = new Map(); +const errors = []; +for (const file of htmlFiles) { + const relative = path.relative(root.pathname, file.pathname); + const pathname = `/${relative.replace(/index\.html$/, '')}`; + const html = await readFile(file, 'utf8'); + const ids = [...html.matchAll(/\bid="([^"]+)"/g)].map(match => match[1]); + if (new Set(ids).size !== ids.length) errors.push(`${pathname}: duplicate IDs`); + pages.set(pathname, { html, ids: new Set(ids) }); + if ((html.match(/]*\bautoplay/.test(html)) errors.push(`${pathname}: autoplay is forbidden`); + if (/]+src="(?!\/brand\/|\/media\/)/.test(html)) errors.push(`${pathname}: unexpected image outside brand/media`); +} +let checkedLinks = 0; +for (const [pathname, { html }] of pages) { + for (const match of html.matchAll(/\b(?:href|src)="([^"]+)"/g)) { + const href = match[1].replaceAll('&', '&'); + if (/^(data:|mailto:|tel:)/.test(href)) continue; + const target = new URL(href, origin + pathname); + if (target.origin !== origin) continue; + checkedLinks++; + const targetPath = decodeURIComponent(target.pathname); + const page = pages.get(targetPath) || pages.get(`${targetPath}/`); + if (!page) { + try { if (!(await stat(new URL(`.${targetPath}`, root))).isFile()) throw new Error(); } + catch { errors.push(`${pathname}: missing ${href}`); } + } else if (target.hash && !page.ids.has(decodeURIComponent(target.hash.slice(1)))) { + errors.push(`${pathname}: missing anchor ${href}`); + } + } +} +const sitemap = await readFile(new URL('sitemap.xml', root), 'utf8'); +for (const pathname of pages.keys()) if (pathname !== '/404.html' && !sitemap.includes(`${origin}${pathname}`)) errors.push(`${pathname}: absent from sitemap`); +await stat(new URL('pagefind/pagefind.js', root)); +for (const filename of ['social-preview.png', 'favicon.svg', 'favicon-16.png', 'favicon-32.png', 'apple-touch-icon.png']) await stat(new URL(`brand/${filename}`, root)); +const scripts = files.filter(file => file.pathname.includes('/_astro/') && file.pathname.endsWith('.js')); +const javascriptBytes = (await Promise.all(scripts.map(async file => gzipSync(await readFile(file)).length))).reduce((total, size) => total + size, 0); +if (javascriptBytes > 15000) errors.push(`Site JS exceeds 15KB gzip: ${javascriptBytes}`); +if (errors.length) throw new Error(`Site validation failed:\n${[...new Set(errors)].join('\n')}`); +console.log(`Site valid: ${pages.size} HTML pages, ${checkedLinks} internal links/anchors, sitemap, metadata and local search. Shared JS: ${javascriptBytes} bytes gzip (search engine lazy-loaded separately).`); diff --git a/website/scripts/content.mjs b/website/scripts/content.mjs new file mode 100644 index 00000000..8cfe3f4c --- /dev/null +++ b/website/scripts/content.mjs @@ -0,0 +1,69 @@ +import path from 'node:path/posix'; +import { createHash } from 'node:crypto'; + +export function normalizeMarkdown(markdown) { + let fence; + return markdown.split('\n').map(line => { + const delimiter = line.match(/^\s*(`{3,}|~{3,})/); + if (delimiter) { + if (!fence) fence = delimiter[1][0]; + else if (delimiter[1][0] === fence) fence = undefined; + } + const preserved = !fence && !delimiter ? line.replace(/ {2,}$/, '
') : line; + return preserved.replace(/[ \t]+$/, ''); + }).join('\n').trimEnd() + '\n'; +} + +export function renderReleaseMarkdown(release) { + return `---\ntitle: ${JSON.stringify(release.name || release.tag_name)}\ntag: ${release.tag_name}\ndate: ${release.published_at.slice(0, 10)}\nurl: ${release.html_url}\n---\n${normalizeMarkdown(release.body || '')}`; +} + +export function assertFileMembership(actual, expected, label) { + if (JSON.stringify([...actual].sort()) !== JSON.stringify([...expected].sort())) throw new Error(`${label} files do not match the provenance manifest. Refresh or review orphan generated files.`); +} + +export function planOwnedRemovals(contents, expected, proofs) { + const removed = Object.keys(contents).filter(name => !expected.includes(name)); + for (const name of removed) { + const hash = createHash('sha256').update(contents[name]).digest('hex'); + if (!proofs[name] || hash !== proofs[name]) throw new Error(`Refusing to remove unverified or edited generated file: ${name}`); + } + return removed; +} + +export function assertReleaseCache(releases, cached, contents) { + const history = releases.filter(release => !release.draft && !release.prerelease && /^v\d+\.\d+\.\d+$/.test(release.tag_name)); + const metadata = history.map(release => ({ tag: release.tag_name, date: release.published_at.slice(0, 10), url: release.html_url })); + const previousMetadata = cached.history.map(({ tag, date, url }) => ({ tag, date, url })); + const latest = selectStable(releases); + if (cached.latest.tag !== latest.tag_name || cached.latest.date !== latest.published_at.slice(0, 10) || cached.latest.url !== latest.html_url || JSON.stringify(metadata) !== JSON.stringify(previousMetadata)) throw new Error('Public release metadata/history changed. Refresh and review the snapshot.'); + assertFileMembership(Object.keys(contents), history.map(release => `${release.tag_name}.md`), 'Release'); + for (const release of history) if (contents[`${release.tag_name}.md`] !== renderReleaseMarkdown(release)) throw new Error(`Published release notes changed: ${release.tag_name}. Refresh and review the snapshot.`); +} + +export function selectStable(releases) { + const stable = releases.filter(release => !release.draft && !release.prerelease && /^v\d+\.\d+\.\d+$/.test(release.tag_name)); + stable.sort((left, right) => { + const leftParts = left.tag_name.slice(1).split('.').map(Number); + const rightParts = right.tag_name.slice(1).split('.').map(Number); + return rightParts[0] - leftParts[0] || rightParts[1] - leftParts[1] || rightParts[2] - leftParts[2]; + }); + if (!stable.length) throw new Error('No verified public stable release found.'); + return stable[0]; +} + +export function resolveSourceLink(href, sourcePath, { commit, routes }) { + if (!href || href.startsWith('#') || href.startsWith('/') || /^[a-z][a-z\d+.-]*:/i.test(href)) return href; + const [pathname, fragment] = href.split('#'); + const resolved = path.normalize(path.join(path.dirname(sourcePath), pathname)); + const anchor = fragment === undefined ? '' : `#${fragment}`; + if (routes[resolved]) return `/docs/reference/${routes[resolved]}/${anchor}`; + return `https://github.com/raiseCatError/notMyShell/blob/${commit}/${resolved}${anchor}`; +} + +export function validateMedia(slot) { + if (!/^[a-z][a-z\d-]+$/.test(slot.id) || !(slot.width > 0 && slot.height > 0) || !slot.caption || !slot.altRequirement) throw new Error(`Invalid media slot: ${slot.id}`); + if (slot.src && (!slot.src.startsWith('/media/') || !['image', 'video'].includes(slot.kind) || !slot.alt)) throw new Error(`Media ${slot.id} needs a local asset, kind and specific alt text.`); + if (slot.kind === 'video' && slot.src && !slot.poster) throw new Error(`Video ${slot.id} needs a poster.`); + return true; +} diff --git a/website/scripts/launch.mjs b/website/scripts/launch.mjs new file mode 100644 index 00000000..d1bfcbca --- /dev/null +++ b/website/scripts/launch.mjs @@ -0,0 +1,11 @@ +export function assertLaunch(launch, releases, requirePublished = false) { + if (!['candidate', 'published'].includes(launch.state) || !/^\d+\.\d+\.\d+$/.test(launch.version) || !/^[a-f\d]{40}$/.test(launch.commit) || !/^\d{4}-\d{2}-\d{2}$/.test(launch.verifiedAt) || !launch.ref) throw new Error('Invalid launch source provenance.'); + if (launch.state === 'candidate') { + if (launch.date || launch.url) throw new Error('A candidate cannot claim a publication date or release URL.'); + if (requirePublished) throw new Error('Launch blocked: the target must be verified as a published release.'); + return; + } + const tag = `v${launch.version}`; + const expectedUrl = `https://github.com/raiseCatError/notMyShell/releases/tag/${tag}`; + if (launch.ref !== tag || launch.url !== expectedUrl || launch.date !== releases?.latest.date || releases?.latest.tag !== tag || releases.sourceCommit !== launch.commit || releases.latest.url !== launch.url) throw new Error('Published launch source does not match the verified public release snapshot.'); +} diff --git a/website/scripts/markdown.mjs b/website/scripts/markdown.mjs new file mode 100644 index 00000000..a3ec86fd --- /dev/null +++ b/website/scripts/markdown.mjs @@ -0,0 +1,39 @@ +import { readFileSync } from 'node:fs'; +import { resolveSourceLink } from './content.mjs'; + +function walk(node, visitor) { + visitor(node); + for (const child of [...(node.children || [])]) walk(child, visitor); +} +export function sourceLinks() { + return (tree, file) => { + const frontmatter = file.data.astro?.frontmatter; + const source = frontmatter?.source; + const tag = frontmatter?.tag; + if (!source && !tag) return; + const manifest = tag ? { commit: tag, routes: {} } : JSON.parse(readFileSync(new URL('../src/data/upstream.json', import.meta.url), 'utf8')); + walk(tree, node => { + if (tag && node.type === 'heading' && node.depth === 1) node.depth = 2; + if (node.type === 'link' || node.type === 'definition' || node.type === 'image') node.url = resolveSourceLink(node.url, source || 'README.md', manifest); + }); + }; +} +export function accessibleMarkup() { + return tree => walk(tree, node => { + if (node.type !== 'element') return; + if (/^h[2-4]$/.test(node.tagName) && node.properties?.id) { + node.children.push({ type: 'element', tagName: 'a', properties: { href: `#${node.properties.id}`, className: ['heading-anchor'], ariaLabel: 'Link to this section' }, children: [] }); + } + if (node.tagName === 'pre') { + node.properties ||= {}; + node.properties.tabIndex = 0; + node.properties.ariaLabel = 'Code example'; + } + if (node.tagName === 'table' && !node.properties?.dataScrollable) { + const table = { ...node, properties: { ...node.properties, dataScrollable: true } }; + node.tagName = 'div'; + node.properties = { className: ['table-scroll'], tabIndex: 0, role: 'region', ariaLabel: 'Scrollable reference table' }; + node.children = [table]; + } + }); +} diff --git a/website/scripts/sync-upstream.mjs b/website/scripts/sync-upstream.mjs new file mode 100644 index 00000000..744e4eb3 --- /dev/null +++ b/website/scripts/sync-upstream.mjs @@ -0,0 +1,108 @@ +import { mkdir, writeFile, readFile, readdir, unlink } from 'node:fs/promises'; +import { createHash } from 'node:crypto'; +import { selectStable, normalizeMarkdown, renderReleaseMarkdown, assertReleaseCache, planOwnedRemovals } from './content.mjs'; +import { sources } from './upstream-sources.mjs'; +import { assertLaunch } from './launch.mjs'; + +const repository = 'raiseCatError/notMyShell'; +const root = new URL('../', import.meta.url); +const writing = process.argv.includes('--write'); +if (!writing && !process.argv.includes('--check')) throw new Error('Choose --check or --write explicitly.'); +const headers = { 'User-Agent': 'NMSh-website-source-sync', Accept: 'application/vnd.github+json' }; +if (process.env.GITHUB_TOKEN) headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`; +async function fetchText(url) { + const response = await fetch(url, { headers, signal: AbortSignal.timeout(20000) }); + if (!response.ok) throw new Error(`${response.status} fetching ${url}. Existing snapshots remain buildable.`); + return response.text(); +} +async function api(route) { return JSON.parse(await fetchText(`https://api.github.com/${route}`)); } +const releases = await api(`repos/${repository}/releases?per_page=100`); +const latest = selectStable(releases); +const stableCommit = await api(`repos/${repository}/commits/${latest.tag_name}`); +const previousLaunch = JSON.parse(await readFile(new URL('src/data/launch.json', root), 'utf8')); +const argument = name => { + const index = process.argv.indexOf(name); + if (index < 0) return undefined; + const value = process.argv[index + 1]; + if (!value || value.startsWith('--')) throw new Error(`${name} requires a value.`); + return value; +}; +const candidateRef = argument('--candidate'); +const publishedTag = argument('--published'); +if (candidateRef && publishedTag) throw new Error('Choose a candidate or a published target, not both.'); +const state = candidateRef ? 'candidate' : publishedTag ? 'published' : previousLaunch.state; +const targetRef = candidateRef || publishedTag || previousLaunch.ref; +const version = argument('--version') || previousLaunch.version; +if (state === 'published' && (targetRef !== `v${version}` || latest.tag_name !== targetRef)) throw new Error('The intended launch tag is not the verified latest public stable release.'); +const commit = await api(`repos/${repository}/commits/${encodeURIComponent(targetRef)}`); +const launch = { state, version, ref: targetRef, commit: commit.sha, verifiedAt: new Date().toISOString().slice(0, 10), ...(state === 'published' ? { date: latest.published_at.slice(0, 10), url: latest.html_url } : {}) }; +assertLaunch(launch, { latest: { tag: latest.tag_name, date: latest.published_at.slice(0, 10), url: latest.html_url }, sourceCommit: stableCommit.sha }); +const tap = await fetchText('https://raw.githubusercontent.com/raiseCatError/homebrew-tap/HEAD/Formula/nmsh.rb'); +const tapVersion = tap.match(/refs\/tags\/(v\d+\.\d+\.\d+)\.tar\.gz/)?.[1]; +if (!tapVersion) throw new Error('Homebrew formula version could not be verified.'); +if (!writing) { + const cached = JSON.parse(await readFile(new URL('src/data/release.json', root), 'utf8')); + if (cached.latest.tag !== latest.tag_name || cached.sourceCommit !== stableCommit.sha || cached.tapVersion !== tapVersion || previousLaunch.commit !== launch.commit || previousLaunch.state !== launch.state || previousLaunch.version !== launch.version) throw new Error('Launch candidate, public release or tap changed. Run sync:upstream and review all content before committing.'); + const names = (await readdir(new URL('src/content/releases/', root))).filter(name => name.endsWith('.md')); + const contents = Object.fromEntries(await Promise.all(names.map(async name => [name, await readFile(new URL(`src/content/releases/${name}`, root), 'utf8')]))); + assertReleaseCache(releases, cached, contents); + console.log(`Upstream agrees: ${state} v${version} at ${commit.sha}; published ${latest.tag_name}, Homebrew ${tapVersion}.`); + process.exit(0); +} +const sha256 = text => createHash('sha256').update(text).digest('hex'); +const files = []; +for (const source of sources) { + const original = await fetchText(`https://raw.githubusercontent.com/${repository}/${commit.sha}/${source.path}`); + const body = normalizeMarkdown(original.replace(/^# .+\r?\n/, '')); + const markdown = `---\ntitle: ${JSON.stringify(source.title)}\ndescription: ${JSON.stringify(`Canonical ${source.title.toLowerCase()} from the v${version} ${state} source.`)}\ngroup: Technical reference\norder: ${sources.indexOf(source) + 100}\nsource: ${JSON.stringify(source.path)}\n---\n${body}`; + files.push({ source, original, markdown }); +} +const history = releases.filter(release => !release.draft && !release.prerelease && /^v\d+\.\d+\.\d+$/.test(release.tag_name)); +const releaseFiles = history.map(release => ({ name: `${release.tag_name}.md`, markdown: renderReleaseMarkdown(release) })); +const assets = []; +for (const name of ['nmsh-lockup.svg', 'nmsh-logo.png']) { + const response = await fetch(`https://raw.githubusercontent.com/${repository}/${commit.sha}/assets/brand/${name}`, { headers, signal: AbortSignal.timeout(20000) }); + if (!response.ok) throw new Error(`Cannot read genuine brand asset: ${name}`); + assets.push({ name, data: Buffer.from(await response.arrayBuffer()) }); +} +async function readPrevious(path) { + try { return JSON.parse(await readFile(new URL(path, root), 'utf8')); } + catch (error) { if (error.code === 'ENOENT') return undefined; throw error; } +} +async function orphanFiles(directory, expected, proofs) { + let names; + try { names = await readdir(new URL(directory, root)); } + catch (error) { if (error.code === 'ENOENT') return []; throw error; } + const entries = await Promise.all(names.filter(name => name.endsWith('.md')).map(async name => [name, await readFile(new URL(directory + name, root), 'utf8')])); + const orphans = planOwnedRemovals(Object.fromEntries(entries), expected, proofs); + return orphans.map(name => new URL(directory + name, root)); +} +const previousManifest = await readPrevious('src/data/upstream.json'); +const previousSnapshot = await readPrevious('src/data/release.json'); +const removals = [ + ...await orphanFiles('src/content/reference/', sources.map(source => `${source.slug}.md`), Object.fromEntries((previousManifest?.files || []).map(file => [`${file.slug}.md`, file.generatedSha256]))), + ...await orphanFiles('src/content/releases/', releaseFiles.map(file => file.name), Object.fromEntries((previousSnapshot?.history || []).map(release => [`${release.tag}.md`, release.generatedSha256]))), +]; +await mkdir(new URL('src/content/reference/', root), { recursive: true }); +await mkdir(new URL('src/content/releases/', root), { recursive: true }); +await mkdir(new URL('src/data/', root), { recursive: true }); +for (const file of files) await writeFile(new URL(`src/content/reference/${file.source.slug}.md`, root), file.markdown); +for (const file of releaseFiles) await writeFile(new URL(`src/content/releases/${file.name}`, root), file.markdown); +for (const file of removals) await unlink(file); +const snapshot = { + verifiedAt: new Date().toISOString().slice(0, 10), sourceCommit: stableCommit.sha, tapVersion, + latest: { tag: latest.tag_name, version: latest.tag_name.slice(1), date: latest.published_at.slice(0, 10), url: latest.html_url }, + requirements: { node: '22+', shells: ['zsh', 'Bash 4.4+', 'Fish'] }, + history: history.map(release => ({ tag: release.tag_name, date: release.published_at.slice(0, 10), url: release.html_url, generatedSha256: sha256(renderReleaseMarkdown(release)) })), +}; +const manifest = { + commit: commit.sha, version, state, + routes: Object.fromEntries(sources.map(source => [source.path, source.slug])), + files: files.map(file => ({ ...file.source, sourceSha256: sha256(file.original), generatedSha256: sha256(file.markdown) })), +}; +await writeFile(new URL('src/data/release.json', root), JSON.stringify(snapshot, null, 2) + '\n'); +await writeFile(new URL('src/data/launch.json', root), JSON.stringify(launch, null, 2) + '\n'); +await writeFile(new URL('src/data/upstream.json', root), JSON.stringify(manifest, null, 2) + '\n'); +await mkdir(new URL('public/brand/', root), { recursive: true }); +for (const asset of assets) await writeFile(new URL(`public/brand/${asset.name}`, root), asset.data); +console.log(`Synced ${files.length} canonical documents at v${version} ${state} (${commit.sha}) and ${history.length} actual public releases. Review before publishing.`); diff --git a/website/scripts/upstream-sources.mjs b/website/scripts/upstream-sources.mjs new file mode 100644 index 00000000..c8bda55d --- /dev/null +++ b/website/scripts/upstream-sources.mjs @@ -0,0 +1,24 @@ +export const sources = [ + { path: 'ARCHITECTURE.md', slug: 'architecture', title: 'Runtime architecture' }, + { path: 'docs/architecture/shell-adapter.md', slug: 'shell-adapter', title: 'ShellAdapter contract' }, + { path: 'docs/architecture/platforms.md', slug: 'platforms', title: 'Platform evidence and limitations' }, + { path: 'docs/architecture/context-modules.md', slug: 'context-modules', title: 'Context modules and packs' }, + { path: 'docs/architecture/prompt-customization.md', slug: 'prompt-customization', title: 'Native prompt customization' }, + { path: 'docs/architecture/optional-tools.md', slug: 'optional-tools', title: 'Optional tools and providers' }, + { path: 'docs/design/theme-bridge.md', slug: 'theme-bridge', title: 'Theme Bridge design and ownership' }, + { path: 'docs/design/configured-completion.md', slug: 'configured-completion', title: 'Configured completion' }, + { path: 'docs/design/session-interaction-ux.md', slug: 'session-interaction', title: 'Session interaction design' }, + { path: 'docs/privacy.md', slug: 'privacy', title: 'Privacy and local data' }, + { path: 'docs/accessibility/baseline.md', slug: 'accessibility', title: 'Accessibility baseline' }, + { path: 'docs/testing/v020-physical-qa.md', slug: 'physical-qa', title: 'v0.20 physical QA checklist' }, + { path: 'docs/development-verification.md', slug: 'verification', title: 'Development verification' }, + { path: 'SECURITY.md', slug: 'security', title: 'Security policy' }, + { path: 'CONTRIBUTING.md', slug: 'contributing', title: 'Contributing to NMSh' }, + { path: 'CHANGELOG.md', slug: 'changelog', title: 'Canonical changelog' }, + { path: 'docs/design/terminal-workflow.md', slug: 'terminal-workflow', title: 'Terminal workflow design' }, + { path: 'docs/design/background-tasks.md', slug: 'background-tasks', title: 'Background task architecture' }, + { path: 'docs/design/workspace-keymap.md', slug: 'workspace-keymap', title: 'Workspace keymap' }, + { path: 'docs/architecture/github-workspace.md', slug: 'github-workspace', title: 'Read-only GitHub workspace' }, + { path: 'docs/development/copy-actions-slice-3.md', slug: 'copy-actions', title: 'Output selection and Copy Actions' }, + { path: 'docs/testing/post-v020-preferences-onboarding.md', slug: 'onboarding', title: 'First-run behavior and validation' }, +]; diff --git a/website/scripts/validate-content.mjs b/website/scripts/validate-content.mjs new file mode 100644 index 00000000..39c4b759 --- /dev/null +++ b/website/scripts/validate-content.mjs @@ -0,0 +1,33 @@ +import { readFile, access, readdir } from 'node:fs/promises'; +import { createHash } from 'node:crypto'; +import { validateMedia, assertFileMembership } from './content.mjs'; +import { assertLaunch } from './launch.mjs'; + +const root = new URL('../', import.meta.url); +const read = async path => JSON.parse(await readFile(new URL(path, root), 'utf8')); +const release = await read('src/data/release.json'); +const manifest = await read('src/data/upstream.json'); +const launch = await read('src/data/launch.json'); +assertLaunch(launch, release, process.argv.includes('--require-published')); +if (launch.version !== manifest.version || launch.commit !== manifest.commit || launch.state !== manifest.state) throw new Error('Launch and canonical source snapshots disagree.'); +assertFileMembership((await readdir(new URL('src/content/reference/', root))).filter(name => name.endsWith('.md')), manifest.files.map(file => `${file.slug}.md`), 'Reference'); +assertFileMembership((await readdir(new URL('src/content/releases/', root))).filter(name => name.endsWith('.md')), release.history.map(item => `${item.tag}.md`), 'Release'); +for (const file of manifest.files) { + const markdown = await readFile(new URL(`src/content/reference/${file.slug}.md`, root)); + const hash = createHash('sha256').update(markdown).digest('hex'); + if (hash !== file.generatedSha256) throw new Error(`Generated reference changed: ${file.slug}. Edit upstream, not the snapshot.`); +} +for (const item of release.history) { + const markdown = await readFile(new URL(`src/content/releases/${item.tag}.md`, root)); + if (createHash('sha256').update(markdown).digest('hex') !== item.generatedSha256) throw new Error(`Generated release notes changed: ${item.tag}. Refresh from the public release, not a hand-edited snapshot.`); +} +const slots = await read('src/data/media.json'); +if (new Set(slots.map(slot => slot.id)).size !== slots.length) throw new Error('Duplicate media IDs.'); +for (const slot of slots) { + validateMedia(slot); + if (slot.src) await access(new URL(`public${slot.src}`, root)); + if (slot.poster) await access(new URL(`public${slot.poster}`, root)); +} +const guides = await readdir(new URL('src/content/guides/', root)); +if (guides.filter(file => file.endsWith('.md')).length < 18) throw new Error('The complete curated guide set is missing.'); +console.log(`Content valid: ${manifest.files.length} pinned sources, ${guides.length} guides, ${slots.length} media slots; launch v${launch.version} ${launch.state}, public stable ${release.latest.tag}.`); diff --git a/website/src/brand/social-preview.svg b/website/src/brand/social-preview.svg new file mode 100644 index 00000000..ee58ccd4 --- /dev/null +++ b/website/src/brand/social-preview.svg @@ -0,0 +1,10 @@ + + + + notMyShell + + A richer way to use + the terminal. + + nmsh.raisecaterror.com + diff --git a/website/src/components/Code.astro b/website/src/components/Code.astro new file mode 100644 index 00000000..17e334ce --- /dev/null +++ b/website/src/components/Code.astro @@ -0,0 +1,9 @@ +--- +import { Code } from 'astro:components'; +interface Props { code: string; lang?: 'sh' | 'text'; label?: string } +const { code, lang = 'sh', label = 'Command' } = Astro.props; +--- +
+
{label}
+ +
diff --git a/website/src/components/DocNavigation.astro b/website/src/components/DocNavigation.astro new file mode 100644 index 00000000..19453a2c --- /dev/null +++ b/website/src/components/DocNavigation.astro @@ -0,0 +1,18 @@ +--- +import { docNavigation, type documents } from '../lib/docs'; +interface Props { entries: Awaited>; slug: string; label: string } +const { entries, slug, label } = Astro.props; +const groups = docNavigation(entries); +--- + diff --git a/website/src/components/Media.astro b/website/src/components/Media.astro new file mode 100644 index 00000000..1b203c40 --- /dev/null +++ b/website/src/components/Media.astro @@ -0,0 +1,26 @@ +--- +import inventory from '../data/media.json'; +interface Props { id: string; compact?: boolean } +interface Slot { id: string; title: string; purpose: string; width: number; height: number; caption: string; altRequirement: string; src?: string; kind?: 'image' | 'video'; alt?: string; poster?: string } +const { id, compact = false } = Astro.props; +const slot = inventory.find(item => item.id === id) as Slot | undefined; +if (!slot) throw new Error(`Unknown media slot: ${id}`); +--- +
+
+ {slot.src && slot.kind === 'video' ? ( + + ) : slot.src ? ( + {slot.alt} + ) : ( + + )} +
+
{slot.caption}
+
diff --git a/website/src/components/Search.astro b/website/src/components/Search.astro new file mode 100644 index 00000000..53bbd98e --- /dev/null +++ b/website/src/components/Search.astro @@ -0,0 +1,10 @@ + +

Documentation

+ +

Try “sessions” or “Theme Bridge”. Search runs in your browser.

+
    + +
    diff --git a/website/src/content.config.ts b/website/src/content.config.ts new file mode 100644 index 00000000..fb33ddf7 --- /dev/null +++ b/website/src/content.config.ts @@ -0,0 +1,13 @@ +import { defineCollection } from 'astro:content'; +import { z } from 'astro/zod'; +import { glob } from 'astro/loaders'; + +const documentSchema = z.object({ + title: z.string(), description: z.string(), group: z.string(), order: z.number(), + source: z.string().optional(), sources: z.array(z.string()).default([]), +}); +export const collections = { + guides: defineCollection({ loader: glob({ pattern: '**/*.md', base: './src/content/guides' }), schema: documentSchema }), + reference: defineCollection({ loader: glob({ pattern: '**/*.md', base: './src/content/reference' }), schema: documentSchema }), + releases: defineCollection({ loader: glob({ pattern: '*.md', base: './src/content/releases' }), schema: z.object({ title: z.string(), tag: z.string(), date: z.coerce.date(), url: z.url() }) }), +}; diff --git a/website/src/content/guides/accessibility.md b/website/src/content/guides/accessibility.md new file mode 100644 index 00000000..699d5295 --- /dev/null +++ b/website/src/content/guides/accessibility.md @@ -0,0 +1,50 @@ +--- +title: Accessibility +description: Reduced motion, safe glyphs, color controls and known assistive-technology limits. +group: Understand the system +order: 14 +sources: [docs/accessibility/baseline.md, README.md] +--- + +## Choose quieter presentation + +NMSh-owned surfaces support reduced motion, safe glyphs and color controls. +These affect frontend presentation, not arbitrary output written by your programs. + +```sh +NMSH_REDUCED_MOTION=1 NMSH_ICONS=safe nmsh +``` + +`NMSH_REDUCED_MOTION=1` holds decorative motion still while durations continue to +count. `NMSH_ICONS=safe` requests ASCII-safe glyphs when fonts or character widths +make decorative symbols unreliable. + +## Color controls + +| Environment | Effect | +| --- | --- | +| Non-empty `NO_COLOR` | Suppress NMSh-generated color escapes | +| `TERM=dumb` | Also suppress NMSh-generated color escapes | +| `NMSH_COLOR=none` | Explicit no-color override | +| `NMSH_COLOR=256` | Explicit xterm 256-color mapping | +| `NMSH_COLOR=truecolor` | Explicit full-color override | + +The explicit `NMSH_COLOR` choice wins over `NO_COLOR` and `TERM`. Bold, inverse +and glyphs can remain without color. The canonical baseline includes known gaps, +including command-row cues that still depend partly on color. + +## Keyboard and terminal behavior + +NMSh actions are intended to remain keyboard reachable. Mouse interaction adds +shortcuts, not a second required input path. The host must forward the relevant +key combinations; Ctrl+J is the fallback when Shift+Enter is unavailable. + +## Screen readers: not a guarantee + +NMSh redraws alternate-screen terminal frames. Screen-reader behavior depends on +the terminal and assistive technology and has not been verified. Text-carried +state is useful, but **does not establish full screen-reader accessibility**. + +This website separately provides semantic HTML, keyboard navigation, local search, +light/dark themes and reduced-motion styling. Website accessibility does not imply +the terminal application has the same assistive-technology support. diff --git a/website/src/content/guides/advanced.md b/website/src/content/guides/advanced.md new file mode 100644 index 00000000..77371593 --- /dev/null +++ b/website/src/content/guides/advanced.md @@ -0,0 +1,44 @@ +--- +title: Advanced usage +description: Terminal integration, tmux boundaries and deliberate shell escape routes. +group: Understand the system +order: 16 +sources: [README.md, ARCHITECTURE.md, docs/architecture/multiplexer-interop.md] +--- + +## Make the host launch the frontend + +Keep zsh, Bash or Fish as your login shell. Configure your terminal's launch +command instead of using `chsh`. An absolute path can avoid GUI PATH differences. +Host configuration is executable or operational configuration: inspect it before +changing it, and keep an ordinary terminal profile available for recovery. + +## Work alongside tmux + +NMSh is not a multiplexer. tmux owns panes and its own terminal layer; NMSh owns +the interaction within its frontend. `/tmux` is a configuration studio, not a +replacement for the tmux process. Nested terminal capabilities can differ, so +test your actual host/tmux combination before depending on advanced keys or mouse. + +## Hand control back deliberately + +`/zsh` hands off to an ordinary interactive zsh. `/exit` uses your configured +default shell. These are intentional escape routes, not aliases for launching a +recursive NMSh instance. Recursive NMSh within its own managed shell is unsupported. + +Fullscreen applications use passthrough automatically when recognized. That path +preserves the real program's terminal interaction instead of converting it into +NMSh widgets. Compatibility with every terminal protocol is not guaranteed. + +## Keep automation separate from presentation + +Use the actual shell and programs for scripts and automation. NMSh slash actions +are frontend controls, not portable shell builtins. A script should not assume +that a theme, animation or folded transcript changes the underlying stdout or +exit status. `/copy` remains plain text. + +## Experimental work is not stable functionality + +Release branches, current implementation slices and proposals can describe +capabilities absent from your installed release. Consult the [roadmap](/roadmap/) +and verified [release history](/releases/) before treating a design as an API. diff --git a/website/src/content/guides/commands.md b/website/src/content/guides/commands.md new file mode 100644 index 00000000..82c2ce33 --- /dev/null +++ b/website/src/content/guides/commands.md @@ -0,0 +1,66 @@ +--- +title: Command reference +description: Find frontend actions by purpose, with links to deeper guides. +group: Understand the system +order: 18 +sources: [README.md, src/commands/slashCommands.ts] +--- + +This is a map of common actions in v0.20.0, not a replacement for the generated +`/help` in your installed version. A slash action controls NMSh; ordinary programs +and absolute executable paths still run in your real shell. + +## Interaction and sessions + +| Action | Purpose | +| --- | --- | +| `/help`, `/palette` | Browse available actions | +| `/layout` (`/composer`) | Composer placement and layout | +| `/syntax`, `/cursor` | Input presentation | +| `/transcript` | Transcript settings | +| `/history`, `/find`, `/filter`, `/copy` | Recall, inspect, narrow and copy | +| `/actions`, `/compare` | Act on completed output and compare results | +| `/queue`, `/pause`, `/qr`, `/qc` | Serial execution, pause and reviewed resume/clear | +| `/background` (`/bg`) | Separate session-owned background tasks | +| `/pins`, `/clips`, `/ps` | Kept commands, in-memory clips and reviewed clipboard paste | +| `/resume`, `/sessions` | Session navigation | +| `/shell` | Switch supported shell backend | +| `/zsh`, `/exit` | Hand off to an ordinary shell | + +## Preferences and appearance + +| Action | Purpose | +| --- | --- | +| `/settings` (`/config`), `/setup`, `/startup` | Preferences, guided setup and starting points | +| `/prompt`, `/modules` | Prompt providers and native modules | +| `/appearance`, `/theme`, `/theme-bridge` | Appearance, palettes and opt-in tool themes | +| `/chroma`, `/motion`, `/glyphs`, `/chrome` | Frontend presentation controls | +| `/strip`, `/screensaver` | Status Strip and idle presentation | + +## Tools and control + +| Action | Purpose | +| --- | --- | +| `/tools`, `/providers`, `/integrations` | Optional tooling and provider health | +| `/configure`, `/tmux`, `/dotfiles` | Reviewed configuration workflows | +| `/claude`, `/ai`, `/mods` | Managed Claude Code, agent surfaces and mod inventory | +| `/worktrees` | Worktree inventory and explicit management plans | +| `/sidebar`, `/panel`, `/keys` | Opt-in experimental workspace regions and bindings | +| `/github` (`/prs`, `/issues`) | Read-only GitHub workspace | +| `/zoomies` (`/awake`, `/caffeinate`) | Keep Awake | +| `/btw` (`/ask`) | Local typed-action guidance | +| `/status`, `/doctor`, `/update` | Status, diagnostics and update plan | + +## CLI entry points + +```sh +nmsh --version +nmsh --sessions +nmsh doctor +nmsh config export --output nmsh-settings.json +``` + +Context Packs are managed through `nmsh packs`; refer to the +[pack reference](/docs/reference/context-modules/) for the format and safety model. +No `/learn` command is available in this release. [Learn Mode](/learn/) is a future +proposal, subject to architecture, curriculum and security approval. diff --git a/website/src/content/guides/completion.md b/website/src/content/guides/completion.md new file mode 100644 index 00000000..8afe69fa --- /dev/null +++ b/website/src/content/guides/completion.md @@ -0,0 +1,44 @@ +--- +title: Commands and completion +description: Shell-aware candidates, providers and the limits of configured completion. +group: Daily work +order: 5 +sources: [README.md, docs/design/configured-completion.md, docs/architecture/optional-tools.md] +--- + +## Two kinds of command discovery + +Type `/` to discover NMSh actions. Type an ordinary shell command and use Tab for +shell-aware completion. `/help` groups actions; `/palette` offers an explicit +action picker. These do not replace your shell's own commands or manual pages. + +## Candidates are not execution + +NMSh renders and inserts candidates into the composer. Completion never submits +the unfinished command to the managed shell. A suggestion is editable text, not +permission to execute it. + +The configured zsh bridge uses a detached helper and a hidden PTY to collect +structured completion data without taking over your terminal. It can load your +trusted shell configuration. That configuration is executable code: detached +terminal ownership is **not** a security sandbox against side effects. + +## Choose providers deliberately + +`/providers` manages the available picker, history, navigation and suggestion +providers. `/tools` explains optional dependencies. A shell-aware native path +remains useful without installing every enhancement. + +## Expect differences between shells + +Fish uses Fish's completion facilities and descriptions. Bash and zsh have their +own adapters and capabilities. NMSh does not promise that every external completion +plugin or widget works unchanged. + +The configured zsh bridge has bounded startup and query deadlines, fallback and +cancellation. Unsupported nested/multiline contexts can fall back. Definitions +created only in the live managed shell are not all replicated into its helper. +Native fzf-tab is not supported; the optional fzf picker is a separate handoff. + +For exact transport and compatibility details, read +[Configured completion](/docs/reference/configured-completion/). diff --git a/website/src/content/guides/composer.md b/website/src/content/guides/composer.md new file mode 100644 index 00000000..546f14e0 --- /dev/null +++ b/website/src/content/guides/composer.md @@ -0,0 +1,44 @@ +--- +title: Composer and input +description: Multiline editing, placement, keyboard forwarding and shell-aware presentation. +group: Daily work +order: 4 +sources: [README.md, docs/design/session-interaction-ux.md, docs/architecture/shell-adapter.md] +--- + +## An editor that belongs to the frontend + +NMSh owns its input editor. Your shell still owns execution, but its ordinary ZLE +or Readline editing UI does not sit on top of the composer. Avoid trying to restore +shell prompt widgets inside NMSh; they compete for the same screen and input. + +| Input | Behavior | +| --- | --- | +| Enter | Submit the composed command | +| Shift+Enter | Insert a newline, if forwarded by the terminal | +| Ctrl+J | Fallback newline | +| Tab | Request completion through NMSh | +| Option+Backspace | Standard word deletion with correct host forwarding | + +Terminal shortcuts may intercept a key before NMSh receives it. Check your host's +key configuration when a combination works differently in an integrated terminal. +Kitty enhanced keyboard reporting improves the combinations NMSh can distinguish. + +## Position and shape + +Open `/layout` to choose Bottom, Top or Flow, and a one-line or two-line composer. +Use `/prompt` for prompt content: layout and prompt are related but separate choices. + +In Flow, scrolling back can move the composer off screen. Editing, pasting or +submitting returns to the newest output and still applies the action. Scrolling +alone does not snap back. Docked layouts keep their own historical-scroll behavior. + +## Semantic highlighting + +The lexical highlighter recognizes common command structure. An asynchronous +semantic service classifies executable names, builtins and aliases against shell +knowledge. **Partial input is not executed to find out what it means.** + +Submitted command lines retain NMSh's semantic presentation in history. Arbitrary +PTY stdout/stderr does not receive semantic recoloring. Highlighting is useful +feedback, not a proof that the full grammar or command is valid or safe. diff --git a/website/src/content/guides/configuration.md b/website/src/content/guides/configuration.md new file mode 100644 index 00000000..f1879ecb --- /dev/null +++ b/website/src/content/guides/configuration.md @@ -0,0 +1,62 @@ +--- +title: Configuration and settings +description: Use supported settings, transfer preferences and preserve ownership. +group: Understand the system +order: 13 +sources: [README.md, docs/privacy.md, docs/design/session-interaction-ux.md] +--- + +## Prefer the supported interface + +`/settings` (alias `/config`) is the main settings surface. `/startup` revisits +starting points and `/setup` opens the guided wizard; +focused studios such as `/theme`, `/prompt`, `/layout` and `/strip` expose related +choices without making you memorize every JSON property. + +NMSh uses its own config directory, normally `~/.config/nmsh`, or an absolute +`$XDG_CONFIG_HOME/nmsh` where configured. `config.json` stores preferences; separate +files hold sessions, presets and integration ownership records. + +Fresh installs receive the Recommended configuration. An existing saved object with +omitted fields retains historical defaults rather than silently receiving every new +visual or retention preference. Explicit choices are preserved. See +[Setup and first run](/docs/setup/) before applying a different starting point. + +Updates are Automatic, Notify only or Off. With Notify, a newer stable release shows +one short amber notice in the transcript, once per version, with the update steps for +this installation: Homebrew commands, `/update` for a verified source checkout, or the +release page. Off checks nothing. + +## Move settings, not the whole history + +```sh +nmsh config export --output nmsh-settings.json +nmsh config import nmsh-settings.json +``` + +Import previews changes before confirmation. Category selection is available via +`--categories`. Exports are settings transfers, not session or secret backups; +host paths and local theme provenance are deliberately treated differently. + +Review the exported text before sharing it. Do not copy runtime sockets or assume +a running process can be moved between machines with a JSON file. + +## Keep other configuration yours + +NMSh's bootstraps are private session files, not replacement shell rc files. +Explicit integrations can offer a narrowly scoped include line or supported config +change. Those are reviewed diffs, not permission to rewrite unrelated settings. + +Theme Bridge removes only content still matching its ownership ledger. Editing +a generated file yourself can intentionally prevent automatic replacement/removal. +Keep that distinction in mind when moving settings to another machine. + +## Diagnose before editing internals + +```sh +nmsh doctor +``` + +Use diagnostics and the relevant studio first. Historical design documents can +contain older schema examples; they are not a reason to paste an entire old config +over a working installation. diff --git a/website/src/content/guides/context.md b/website/src/content/guides/context.md new file mode 100644 index 00000000..bd7a8586 --- /dev/null +++ b/website/src/content/guides/context.md @@ -0,0 +1,53 @@ +--- +title: Context Engine and modules +description: Facts become native modules, then route to the surfaces you choose. +group: Make it yours +order: 11 +sources: [docs/architecture/context-modules.md, SECURITY.md, docs/privacy.md] +--- + +## From a fact to a useful detail + +The pipeline is **Capability → Fact → Module → Surface Router**. A trusted +capability collects bounded context; a module turns it into presentation; the +router chooses where it belongs. Context is collected on demand for visible +modules, with deadlines, cancellation and caching. + +Examples include project identity, runtimes, environment managers, local cloud +profiles, infrastructure context, system information and Git facts. Local cloud +configuration does not mean NMSh calls a cloud API or reads credential values. + +## Choose a surface + +`/modules` opens the manager; `/prompt` exposes native module routing and details. +Main Prompt, Right Context and Context Rail serve different reading needs. +`/strip` composes the Status Strip with top/bottom placement and left/center/right +groups. Width fitting is deliberate; a narrow terminal cannot display everything. + +## Context Packs + +NMSh includes bundled declarative packs and the `nmsh.context-pack/v1` format. +`nmsh packs` manages local pack files with integrity checks and optional SHA-256 +pinning. Packs name core capabilities and presentation; they cannot carry arbitrary +commands, hooks, includes or executable code. + +There is no remote pack registry in this release. Installing a pack does not grant +new execution authority, and opening a repository does not install its suggestions. + +## More project and runtime context + +Native facts include .NET, Swift, Zig, Deno, Elixir, Dart, Flutter, Ruby and PHP. +The shared build module recognizes Bazel, Gradle, Maven, CMake, Meson and Make. +Version-control extras include Mercurial and Jujutsu. Requested versions come from +project files; active versions come from supported install metadata, without +executing a project-controlled runtime binary to ask its version. + +## Discovery must not execute the project + +Context discovery does not source `.envrc`, run PATH binaries to discover versions, +execute kubeconfig credential plugins or run repository scripts. It uses bounded +local parsing and a shell-reported environment allowlist. Display-only sensitive +facts do not enter exported settings or historical prompt data. + +Read the [canonical module reference](/docs/reference/context-modules/) for the +capability inventory, what each reads and the exact security boundaries. diff --git a/website/src/content/guides/core-concepts.md b/website/src/content/guides/core-concepts.md new file mode 100644 index 00000000..9d0fb9f5 --- /dev/null +++ b/website/src/content/guides/core-concepts.md @@ -0,0 +1,44 @@ +--- +title: Core concepts +description: Understand the terminal host, frontend, shell and persistent session. +group: Start here +order: 2 +sources: [ARCHITECTURE.md, docs/architecture/terminal-stack.md] +--- + +## Four layers, different responsibilities + +1. **Terminal host:** the window, fonts, cells, keyboard protocols and terminal selection. +2. **NMSh frontend:** the composer, suggestions, transcript, panels and prompt presentation. +3. **ShellAdapter and PTY:** the shell-specific bootstrap and pseudo-terminal transport. +4. **Your real shell:** parsing, expansion, aliases, functions, environment and jobs. + +NMSh is not a terminal emulator, a replacement shell or a multiplexer. It can run +inside an integrated editor terminal or alongside tmux without replacing either. + +## One shell that keeps its state + +Changing directory, defining a function or exporting a variable affects the +continuing shell. NMSh does not reconstruct shell state after each command. +The normal session service can keep that shell alive independently of a window. + +The **composer** is the editable command area. The **transcript** is NMSh's +presentation of submitted commands and their output. A **live session** includes +an actual running shell. A saved transcript is a record, not a resurrected process. + +## Frontend actions versus shell commands + +`/theme` is an NMSh action. `git status` is interpreted by your shell. The complete +NMSh action list lives in `/help`. Absolute executable paths such as `/usr/bin/env` +are shell commands, not requests to an imaginary slash-command namespace. + +## Interactive programs + +Fullscreen and raw interactive applications use **passthrough**. NMSh suspends +its own presentation and forwards the program's terminal interaction. This is +why an editor is not expected to fit inside a transcript card. + +Compatibility is not universal: terminal protocols, shell plugins and inline +redraws have edge cases. The [architecture reference](/docs/reference/architecture/) +explains the real runtime boundaries; [troubleshooting](/docs/troubleshooting/) +helps when an individual tool does not behave as expected. diff --git a/website/src/content/guides/development.md b/website/src/content/guides/development.md new file mode 100644 index 00000000..d3db8435 --- /dev/null +++ b/website/src/content/guides/development.md @@ -0,0 +1,53 @@ +--- +title: Architecture and development +description: Find runtime boundaries, canonical verification and contribution guidance. +group: Understand the system +order: 17 +sources: [ARCHITECTURE.md, CONTRIBUTING.md, docs/development-verification.md] +--- + +## Start at the boundaries + +The [architecture overview](/architecture/) gives the short explanation. +[Runtime architecture](/docs/reference/architecture/) covers input, rendering, +shell events, services, output and repository structure in depth. + +Preserve the persistent ShellSession, raw PTY presentation, detached semantic +helpers, FOLLOW/DETACHED behavior and fullscreen passthrough. A website change +must not require touching those runtime boundaries. + +## Contributing to the application + +Read the current repository's `AGENTS.md`, architecture and relevant issue before +editing. Feature work normally starts from and targets `dev`; `master` represents +released work. Acceptance criteria live in GitHub issues. Architectural records +can describe development work, so cross-check against a released tag. + +Application verification commands are: + +```sh +npm run verify:fast +npm run verify +``` + +The first is an iteration gate, not final verification. Release-sensitive work +adds `npm run verify:release`. These are **application** commands from the repository +root, not prerequisites for building this website. See +[Development verification](/docs/reference/verification/) for platform gates. + +## Contributing to this website + +The site has its own package, lockfile, source tree and tests under `website/`. +It does not import the application runtime or compile NMSh. Its README explains +content updates, pinned source refresh, media replacement and deployment. + +Canonical reference pages are generated from the verified release tag; edit the +original document in a proper application contribution, not its website snapshot. +Curated guides provide the beginner-friendly path and link directly to that source. + +## Open source and reporting + +NMSh is independent software by **raiseCatError**, licensed GPL-3.0-only. +[Contributions](/docs/reference/contributing/), reproducible bug reports and careful +compatibility evidence are welcome. Automated checks and human physical-terminal +validation are distinct; never describe one as the other. diff --git a/website/src/content/guides/everyday-usage.md b/website/src/content/guides/everyday-usage.md new file mode 100644 index 00000000..abe92c3f --- /dev/null +++ b/website/src/content/guides/everyday-usage.md @@ -0,0 +1,63 @@ +--- +title: Everyday usage +description: A practical route through commands, output, settings and familiar tools. +group: Daily work +order: 3 +sources: [README.md, docs/design/session-interaction-ux.md] +--- + +## Work normally + +Run the commands you already use. Your real shell handles quoting, pipes, +redirection and expansion. NMSh's command feedback adds elapsed time and exit +status; those are observations, not a judgment about whether the output is useful. + +```sh +git status --short +``` + +Use the transcript to revisit earlier output. `/find` searches it; `/filter` +narrows what is shown; `/copy` copies plain text without NMSh presentation chrome. +See [Transcript and history](/docs/transcript/) for the distinction between output +history and recallable command history. + +When several commands belong together, use the [serial queue](/docs/execution/). +For a dev server or log stream that should run alongside the shell, start a separate +background task. These are different execution paths, not interchangeable labels. + +## Keep something useful for later + +`/pins` keeps commands and recipes. A pin stages text for editing; a recipe is +reviewed before it can enter the queue. Neither runs merely because you open it. +`/clips` holds deliberately kept text in this frontend's memory, not a permanent +cross-machine clipboard. `/ps` reads the system clipboard only when you request +it and presents the paste for review before insertion. + +For a result you want to share, open `/actions` and choose an operation for that +completed output. Reports include a review step with best-effort redaction. Plain +copy does not redact secrets; always inspect what you share. + +## Keep input and output comfortable + +`/layout` chooses Bottom, Top or Flow composer placement. Bottom and Top dock the +editor. Flow puts it after the latest output while keeping NMSh's editor and +structured transcript; it does not switch to the host's native scrollback. + +Shift+Enter inserts a newline when the host reports that combination. Ctrl+J is +the fallback. Multiline commands are native editor input, not separate submissions. + +## Explore without enabling everything + +`/tools` explains detected and optional tools. `/providers` selects providers for +features such as picking, history and navigation. Detection is not an instruction +to install or run something. Review any install plan before accepting it. + +`/zoomies` opens Keep Awake without starting it. `/zoomies system 2h` requests a +two-hour system-sleep assertion; `/zoomies stop` ends an assertion NMSh owns. +Display-sleep prevention is unavailable through the Linux systemd inhibitor. + +## When you want less decoration + +Start with `/appearance`, `/motion` and `/glyphs`. You can keep the composer and +transcript without animated decoration or a busy prompt. See +[Accessibility](/docs/accessibility/) for reduced-motion, safe-glyph and color controls. diff --git a/website/src/content/guides/execution.md b/website/src/content/guides/execution.md new file mode 100644 index 00000000..668f3d85 --- /dev/null +++ b/website/src/content/guides/execution.md @@ -0,0 +1,108 @@ +--- +title: Queue and background tasks +description: Run a sequence in your shell, review the queue and keep long-running tools alongside it. +group: Daily work +order: 6.2 +sources: [src/commands/slashCommands.ts, src/prompt/configuration.ts, docs/design/terminal-workflow.md, docs/design/background-tasks.md, docs/development/slice-3-stabilization.md] +--- + +## Two different ways to keep work moving + +A **queue** runs commands one at a time in the same persistent shell. A **background +task** runs in a separate session-owned PTY. Choose the queue when commands need +the shell's continuing state; choose a background task for a dev server or log stream +that should not occupy the foreground. + +## Queue commands in order + +While a command runs, compose the next one and submit it to the queue. Commands run +serially, so a directory or environment change can affect later entries. Program +input takes priority: when the foreground program needs an answer, NMSh must not +treat that answer as a queued command. Ctrl+Q opens queue composition; Ctrl+S sends +composer text to direct program input when that input path is available. + +| Action | Behavior | +| --- | --- | +| `/queue` or `/q` | Inspect and manage the queue | +| `/pause` or `/qp` | Pause pending work | +| `/qr` | Review before resuming | +| `/qc` | Review before clearing | +| `/settings queue` | Queue behavior, timeline, output and motion preferences | +| `/queue history` | Inspect recent settled chains | +| `/queue save-summary` | Review the exact text before saving a summary | + +By default, failure or interruption pauses pending work. Resuming and clearing are deliberate +actions, not automatic recovery. A saved summary records what happened; opening it +does not restore executable queue entries or run anything again. + +An entry can require its own approval; editing it withdraws that approval. Explicitly +marking an entry `always` allows it to run after a plain failure, never after an +interrupt. Review those conditions along with the commands, not just the queue order. + +## When a command needs input + +NMSh uses terminal-state evidence to distinguish running work from a program that +needs an answer. Confirmed password/no-echo and single-key reads go directly to the +program without being displayed or kept. An ordinary line question can be shown as +“Probably waiting for input” rather than being presented as certain. + +The question stays near the composer while you inspect earlier output. Enter answers +the program; Ctrl+Q is the separate route for composing queued work. If the program +ends before you send a reply, NMSh discards that reply instead of running it as a +shell command. These protections do not make arbitrary program prompts trustworthy. + +## Follow the sequence without losing the output + +The execution timeline shows running, recent and upcoming commands. It starts +enabled, with two recent and two upcoming entries and an eight-second +completed-chain display; `/queue settings` (or `/settings queue`) changes them. + +Queued output starts **Inline**. The optional **Fold** setting collapses eligible +successful output while retaining the full text. Failures, interruptions and active +inspection remain visible. Ctrl+O reverses the relevant fold state. Folding does not +discard lines or shorten what plain-text copy receives. + +Fresh Recommended configurations enable typed queue-summary saving. Existing saved +configurations with that field omitted keep the historical Off behavior; selecting +a different starting point can also change the preference. Automatic summaries omit +raw command arguments and output. Check the saved text and retention settings before +sharing it. `/queue history` reviews recent settled chains and `/queue save-summary` +saves the newest one after an exact-text review. + +## Review multiline pastes + +A multiline paste can be run as one block, queued separately, kept for editing or +cancelled. Tab, arrows and number keys move the choice; **Enter confirms**. NMSh +refuses separate queueing when it cannot safely split the pasted shell structure. +Reviewing a paste is not a guarantee that its commands are safe. + +## Run a background task + +```sh +/background run tail -f ./application.log +``` + +Use an actual log path in place of `./application.log`. `/bg run` is an alias. +The task inherits the shell's working directory and exported PATH. It does **not** +inherit shell aliases, functions or unexported variables, and it does not become a +job in the foreground shell. + +Open `/background` for the manager. Up from an idle composer reaches the agent +shelf first if present, then the task bar; Shift+Up can focus the task bar. Enter +opens output, `x` requests a stop, and Delete removes a finished task. Esc returns. +Stronger stop signals are paced; a held key must not skip straight to killing. +“Stopping” is a request state, not proof that the process has exited. + +Task output is bounded and kept separate from the shell transcript. It does not +enter `/copy`, reports or comparisons. Tasks survive frontend detach and return +with the live session; ending that session ends its tasks. This requires the +matching session service and POSIX process groups on macOS, Linux or WSL 2. + +Background suggestions start **Off**. `/background suggestions on` opts in to +recognized command-family suggestions. In that prompt, `b` backgrounds once, +Enter runs in the foreground, `a` remembers Always, `n` remembers Never and Esc +returns to the draft. Ordinary commands do not move to the background by themselves. + +See [background task architecture](/docs/reference/background-tasks/) and +[terminal workflow design](/docs/reference/terminal-workflow/) for the underlying +contracts and historical design context. diff --git a/website/src/content/guides/getting-started.md b/website/src/content/guides/getting-started.md new file mode 100644 index 00000000..9114636e --- /dev/null +++ b/website/src/content/guides/getting-started.md @@ -0,0 +1,66 @@ +--- +title: Getting started +description: Install NMSh, start a real shell session and learn your way around. +group: Start here +order: 1 +sources: [README.md] +--- + +NMSh runs **inside your terminal**, around your existing shell. Keep Ghostty, +Terminal.app, Zed, VS Code or another compatible terminal host. You do not need +to replace your shell configuration or learn a new command language. + +## Install and launch + +On macOS, the recommended installation method is Homebrew: + +```sh +brew install raiseCatError/tap/nmsh +nmsh +``` + +The tap can trail the latest public release. The [installation page](/install/) +shows the independently verified release and tap versions, plus exact source-build +instructions. NMSh requires Node.js 22+ and zsh, Bash 4.4+ or Fish. + +These docs describe the published v0.20.0 release. If the tap still provides an +older version, the installation page says so and gives the exact source build. +On first launch, [Setup](/docs/setup/) offers a starting point and reviews it before applying anything. + +> **Do not use `chsh` to make NMSh your login shell.** NMSh is a frontend, not a +> shell. Launch it from your terminal or configure your terminal to start it. + +## Try a familiar command + +Type `pwd` and press Enter. Your actual shell runs it. Now try `cd` into a directory +and run `pwd` again: the working directory persists because this is one continuing +shell, not a series of unrelated subprocesses. + +The composer is where you edit. Output appears in the transcript. NMSh adds command +status, timing and presentation around the output; it does not rewrite arbitrary +program output with semantic colors. + +## Find your bearings + +| Action | Where to go | +| --- | --- | +| Explore available NMSh actions | Type `/`, or open `/help` | +| Adjust preferences | `/settings` | +| Move the composer | `/layout` | +| Choose a theme | `/theme` | +| Revisit sessions | `/resume` | +| Inspect your environment | `/status` or `/doctor` | + +Slash commands belong to NMSh. Ordinary commands, pipelines and scripts still +belong to your selected shell. F1 opens the command palette when the host forwards it. + +## Leave the frontend + +`/exit` hands off to your configured default ordinary shell. `/zsh` deliberately +hands off to ordinary interactive zsh. Closing the terminal window normally detaches +a service-backed session; that is not the same as stopping its running programs. +Read [Sessions and resume](/docs/sessions/) before relying on this behavior. + +Physical-terminal evidence for v0.20.0 is partial and macOS-only; see +[compatibility](/docs/shells/) for limitations rather than assuming every host +and shell configuration behaves identically. diff --git a/website/src/content/guides/integrations.md b/website/src/content/guides/integrations.md new file mode 100644 index 00000000..f2be341e --- /dev/null +++ b/website/src/content/guides/integrations.md @@ -0,0 +1,85 @@ +--- +title: Tools and integrations +description: Curated tools, frameworks, managed targets and repository workspaces. +group: Make it yours +order: 10 +sources: [README.md, docs/architecture/optional-tools.md, docs/architecture/github-workspace.md] +--- + +## Start with what is already installed + +`/tools` detects and explains a curated tool list. `/providers` selects providers; +`/integrations` reports integration health. Detection reads facts; it is not blanket +authorization to install, source configuration or execute workspace code. + +Tools come in tiers. **Recommended** is ripgrep, fd, fzf, jq, bat, zoxide and +tealdeer; **Enhanced** adds tools such as eza, gh, lazygit, delta, Atuin, jc, Glow, +xh, direnv, ShellCheck and shfmt; the rest of the curated catalog sits under All. +`V` switches the view (Recommended + Enhanced by default) without changing what is +selected. Space selects a tool, R and E add the missing Recommended or +Recommended + Enhanced tools, C clears; `/` focuses search so keys never type into it. +The same panel opens from `/tools`, Settings → Tools, the guided setup and the +starting point's review. + +A check that fails or does not finish reads *Unable to verify* and is never selected +or installed; a tool without an installer on this system reads *Unsupported here*. +Installing zoxide, fzf or Atuin never turns on their shell hooks. + +Install plans show typed package-manager arguments and require confirmation. Enter +reviews the exact command for every selected tool, then one confirmation (No is +focused first) leads to per-tool results. +Some tools need manual installation; NMSh does not run special installers such +as an Oh My Zsh install script. + +## Configuration is code until proven otherwise + +`/configure`, `/tmux` and `/dotfiles` expose supported configuration workflows. +Frameworks such as Oh My Zsh, Prezto, Zim, zinit and Antidote are detected from files. +Shell configuration is executable, not harmless data to merge automatically. +Review exact diffs and ownership before accepting changes. + +## Agents, without making NMSh an AI terminal + +`/claude` opens managed Claude Code targets, with launch profiles, structured +permission and question handling, and a conversation workspace. Claude Code must +already be installed. This integration remains beta. + +Inside a managed Claude conversation, `/model` and `/effort` open native pickers. +Choices come from provider telemetry; a requested change is not shown as confirmed +until Claude reports it. The context panel identifies the source of its runtime facts. + +Codex and OpenCode activity can be observed, but they are not controlled as managed +targets. Codex adapter code exists without an integrated frontend path; it does not +establish feature parity with managed Claude Code. + +`/mods` inventories several extension-related surfaces, including declarative +Context Packs. These do not all have the same authority or execution model. Ordinary +Claude plugin inventory is read-only. Plugin toggling stays behind an experimental +gate; the normal UI names the upstream command for you to run yourself instead of +silently editing Claude settings. + +## Repositories + +`/worktrees` lists worktrees, stages navigation and offers explicit create/remove +plans; removal is never forced and does not delete the branch. Enter stages `cd` +only into an empty composer. A related PR appears only when the last `/github` +listing has matching same-repository branch evidence; `p` opens that PR. + +`/github` (also `/prs`, `/issues`) is a **read-only** workspace using your own `gh` +login and a github.com origin. It can inspect PRs, issues, checks, reviews, files +and diffs. It does not submit reviews, merge PRs or write to GitHub. + +## Optional workspace regions + +The workspace sidebar and detail panel are experimental and **Off by default**. +Enable them in Settings → Layout → Workspace sidebar & panel. `/sidebar` and `/panel` +open the regions; their `focus` forms give them the keyboard. Esc returns to the +shell and Tab moves between regions. Wide terminals dock them; narrow terminals +use a focused drawer. Fullscreen programs regain the terminal's width. + +`/keys` explains optional workspace bindings and host or tmux conflicts. These are +opt-in, not replacements for normal composer keys. `/worktrees`, `/github`, sessions +and [background tasks](/docs/execution/) work independently of the regions. + +NMSh complements your editor and terminal. It does not replace project editing, +an editor's search or every upstream tool's interface. diff --git a/website/src/content/guides/prompts.md b/website/src/content/guides/prompts.md new file mode 100644 index 00000000..d1941a53 --- /dev/null +++ b/website/src/content/guides/prompts.md @@ -0,0 +1,40 @@ +--- +title: Prompts and appearance +description: Native modules, external providers and comfortable presentation. +group: Make it yours +order: 8 +sources: [README.md, docs/architecture/prompt-customization.md] +--- + +## Choose who supplies the prompt + +`/prompt` lets you use NMSh Native, Starship, Oh My Posh, Powerlevel10k or no prompt +provider. The composer remains NMSh's editor in every case. An external provider +does not restore that shell theme's whole interactive UI. + +NMSh Native uses semantic modules and independent choices for geometry, palette, +vibrance, Chroma and motion. Changing a color treatment should not require changing +what your prompt tells you. The [Native prompt reference](/docs/reference/prompt-customization/) +describes the supported styles and their settings in detail. + +## Keep context intentional + +`/modules` opens the module manager. Modules can route to Main Prompt, Right Context, +Context Rail or the Status Strip where supported. Choose what matters to the task +rather than filling every available row. `/strip` manages Status Strip composition. + +## Provider limitations + +Powerlevel10k is rendered through a bounded isolated helper. Its instant prompt, +right prompt and gitstatus daemon are not reproduced. Settings defined only in +`.zshrc` rather than its own config are not necessarily visible to the helper. + +External providers are optional installed programs. NMSh's fallbacks are not a +promise of perfect layout parity with every upstream theme or plugin. + +## Personality is optional + +`/appearance`, `/motion`, `/chroma`, `/screensaver` and `/glyphs` control NMSh-owned +presentation. Vespyr is the cat in the product identity and optional visual surfaces. +Reduced motion, safe glyphs and restrained colors are first-class alternatives. +Raw program output and copied text remain outside this decoration system. diff --git a/website/src/content/guides/sessions.md b/website/src/content/guides/sessions.md new file mode 100644 index 00000000..44b96246 --- /dev/null +++ b/website/src/content/guides/sessions.md @@ -0,0 +1,44 @@ +--- +title: Sessions and resume +description: Understand live-shell continuity, explicit attachment and saved transcripts. +group: Daily work +order: 7 +sources: [README.md, ARCHITECTURE.md, docs/architecture/session-presets.md] +--- + +## A window is not the shell + +Normally a separate local session service owns the shell PTY. Closing a frontend +window detaches instead of killing that shell, so running work can continue. +An in-process fallback exists; do not assume service-backed continuity if the +environment cannot start the service. + +This is local process continuity, not cloud execution. Rebooting, terminating the +service or killing the shell is different from closing a terminal window. + +## Return to a live session + +Inside NMSh, open `/resume` or `/sessions`. Outside it, list sessions and explicitly +attach using the ID from the list: + +```sh +nmsh --sessions +nmsh --attach +``` + +`` is a placeholder to replace, not literal text to paste unchanged. +A saved transcript is still useful after a process ends, but reading it does not +recreate shell jobs or replay commands. + +## Starting configurations + +Session presets describe explicitly chosen starting directories and commands. +`nmsh --presets` lists them without executing. Launching a preset creates a new +session; it is not attachment to an old process. Treat preset commands as real +commands with real effects and review what you save. + +## Before removing NMSh + +Finish or deliberately stop live work first. Source uninstall refuses while live +sessions are running. Closing a window is not sufficient evidence that its jobs +have ended. See [Installation](/install/#uninstall) for ownership-preserving removal. diff --git a/website/src/content/guides/setup.md b/website/src/content/guides/setup.md new file mode 100644 index 00000000..e72bb513 --- /dev/null +++ b/website/src/content/guides/setup.md @@ -0,0 +1,96 @@ +--- +title: Setup and first run +description: Choose a starting point, review the changes and return to setup whenever you need it. +group: Start here +order: 2.5 +sources: [src/setup/SetupCat.ts, src/setup/startup/startingPoints.ts, src/setup/startup/view.ts, src/app/TerminalApp.ts, docs/testing/post-v020-preferences-onboarding.md, docs/testing/first-run-window-maximization.md] +--- + +## Choose how NMSh starts + +The first-run selector offers four starting points. Highlighting one previews a +draft; it does not apply that configuration. Enter opens a review before Apply. + +| Starting point | What it does | +| --- | --- | +| NMSh Recommended | Lavender Native presentation, richer motion and a Chat transcript | +| Minimal Terminal | A quiet Native Minimal prompt, Flow placement and no decorative motion | +| Customize Everything | Guided setup over your current configuration, with review before saving | +| Keep Existing | Read-only environment detection to preserve your prompt and existing setup | + +Up and Down move between choices; 1–4 jump to a choice without applying it. Enter +reviews it, then Enter applies the reviewed draft. Customize instead opens the +guided wizard. For Minimal, Left/Right or Tab +switches the One-line/Two-line choice; it starts at Two-line. Esc opens a leave +confirmation with **Stay** selected first. + +The review also has a **Tools** line, changed with T. Recommended defaults to +reviewing and installing the missing Recommended + Enhanced tools that NMSh could +verify, after the ending; Minimal and Keep Existing default to Skip. Nothing is +installed without the tools review and its own confirmation (see +[Tools and integrations](/docs/integrations/)). + +Keep Existing distinguishes detected evidence from assumptions. Besides the prompt it +finds, it can offer NMSh Native or another installed prompt provider. If the prompt choice +is ambiguous, review the offered provider options rather than assuming every shell +customization can be inferred. On an already configured installation it preserves +saved settings without re-detection unless you explicitly request it with R. + +## Reactive Horizon + +The selector places the choices below an Aurora sky whose mood follows the highlight. +Vespyr is composited on the horizon with transparent surrounding cells. Wider terminals +have room for the choice details; smaller layouts adapt rather than requiring a +particular window size. + +A cycles the saved backdrop preference through Aurora, Subtle and Off. This backdrop +choice saves immediately, unlike the configuration draft waiting for Apply. Reduced +Motion and color/glyph capability gates still apply. Applying folds the sky away and +brings the real composer into place; reduced motion goes directly to the settled view. +The transition does not run a demonstration command or simulate shell output. + +A genuine first run adds Vespyr's welcome once, whichever starting point you apply; +later applies add a single receipt line. Minimal keeps no welcome on later launches. + +The first-run introduction is remembered separately from setup completion. Attaching +to a session and explicit setup entry points do not replay installation onboarding. +A failed configuration save keeps the draft and reports the error. + +On supported, verified local host windows, a genuine first run can offer temporary +maximization: M accepts, K keeps the current size. It never happens automatically, +does not edit terminal configuration and is not a dependency of onboarding. Unsupported +hosts, remote sessions and uncertain window ownership keep the ordinary layout. +The adapters target Terminal.app and socket-enabled kitty on macOS, plus supported +Hyprland/Lua setups on Linux. macOS Ghostty, editor terminals, SSH, multiplexers and +WSL do not receive this offer. Actual maximize/restore behavior still needs physical +acceptance; unit fixtures are not evidence of a tested desktop interaction. + +## Return to the starting points + +Use `/startup`, `/setup quick` or Settings → Starting point to reopen the selector. +Use `/setup` to enter the guided wizard directly. Its visual steps follow +**Look → Prompt → Motion → Cursor**, and it reviews changes before saving them. + +## Revisit a specific area + +| Action | Opens | +| --- | --- | +| `/setup` | Guided setup using your current saved choices | +| `/startup` or `/setup quick` | Starting-point selector | +| `/setup look` | Theme, frontend chrome and Chroma | +| `/setup prompt` | Prompt provider and style | +| `/setup motion` | Reduced motion and visual effects | +| `/setup cursor` | Cursor shape, effects and colors | +| `/setup syntax` | Editor, syntax colors and suggestions | +| `/setup transcript` | Transcript colors, dividers and folding | +| `/setup tools` | Optional tools and installation suggestions | + +Opening setup is not permission to install every detected tool or rewrite your +shell configuration. Review individual changes and installation plans. Use +`/settings` for the general preferences surface, or a focused studio such as +`/theme`, `/layout` or `/strip`. + +These instructions describe the published [v0.20.0 release](/releases/). +[First-run behavior and validation](/docs/reference/onboarding/) records its defaults +and the acceptance evidence behind them. [Learn Mode](/learn/) is separate: +it remains a future learning proposal, not part of setup. diff --git a/website/src/content/guides/shells.md b/website/src/content/guides/shells.md new file mode 100644 index 00000000..9c465f21 --- /dev/null +++ b/website/src/content/guides/shells.md @@ -0,0 +1,50 @@ +--- +title: Shell and platform compatibility +description: Supported backends, terminal hosts and honestly stated validation limits. +group: Understand the system +order: 12 +sources: [README.md, docs/architecture/shell-adapter.md, docs/architecture/platforms.md] +--- + +## Three real shell backends + +| Shell | Requirements and limits | +| --- | --- | +| zsh | Default backend; NMSh owns input instead of ZLE UI | +| Bash | 4.4+; macOS's bundled Bash 3.2 is too old | +| Fish | Native Fish adapter; completion and history use Fish semantics | + +`/shell` switches the current backend with safety checks. Jobs and session state +matter: switching is not just changing the text in a shell label. PowerShell and +Nushell are not supported shell backends. + +Your startup configuration is loaded through a controlled private bootstrap. +NMSh does not silently rewrite your rc files. Prompt widgets, instant prompts, +and editor plugins that expect to own the UI may need ordinary-shell use instead. + +## Platform maturity + +| Platform | Current evidence | +| --- | --- | +| macOS | Primary platform; v0.20.0 first-run setup and Tools accepted physically, other checklist items not recorded | +| Linux | Beta; Ubuntu CI and Fedora subset, no physical validation yet | +| Windows through WSL 2 | Beta supported path; detection fixtures, no real WSL physical QA | +| WSL 1 | Detected but not supported | +| Native Windows | Not supported | + +Install and run Linux Node.js and NMSh **inside WSL 2**, not a native Windows Node +process. Runtime sockets must use a private Linux filesystem path, not `/mnt/c`. + +## Terminal hosts + +NMSh does not require a particular host. Ghostty, Terminal.app and editor-integrated +terminals have different capability profiles. Advanced key combinations, mouse, +clipboard helpers and terminal selection depend partly on that outer host. + +Linux copy uses `wl-copy` or X11 helpers when available; notifications need a +reachable desktop environment. Missing optional helpers should be reported, not +treated as proof that the underlying shell cannot run. + +See [platform evidence](/docs/reference/platforms/) and the +[physical QA record](/docs/reference/physical-qa/). Automated testing is not +physical acceptance in Ghostty, Terminal.app, Linux or WSL. diff --git a/website/src/content/guides/themes.md b/website/src/content/guides/themes.md new file mode 100644 index 00000000..7cfcb337 --- /dev/null +++ b/website/src/content/guides/themes.md @@ -0,0 +1,43 @@ +--- +title: Themes, Chroma and Theme Bridge +description: Shape NMSh's palette and optionally extend it to supported tools. +group: Make it yours +order: 9 +sources: [README.md, docs/design/theme-bridge.md, docs/design/chroma-and-ui-chrome.md] +--- + +## Theme Studio + +Open `/theme` to browse built-in themes, inspect imports and edit custom themes. +Imported themes become ordinary NMSh theme data; they do not require the original +application to stay installed or connect to a remote service. + +Built-ins are immutable. Duplicate one when you want an editable copy. Imports +show what maps cleanly and what is ignored, instead of pretending every source +theme concept has a direct NMSh equivalent. + +## Chroma is presentation + +`/chroma` adds optional color treatments to NMSh-owned presentation. Motion is +controlled separately. These settings never add NMSh ANSI decoration to `/copy` +and do not semantically recolor arbitrary PTY output. + +## Extend a palette carefully + +`/theme-bridge` is opt-in. It can carry a palette to supported targets including +fzf, less/man, file listings, bat, tmux, Vim, Neovim and Helix. Generated files and +reviewed include lines are tracked with ownership records. + +Review the exact plan before applying it. NMSh only replaces or removes files it +can still prove it wrote. If a managed file has changed independently, it must not +silently overwrite your work. + +> Theme Bridge affects new tool instances. It does not recolor editors and shells +> already running outside NMSh. It does not manage delta or rewrite your Git config. + +## Imports are not executable themes + +Supported imports are bounded local data parsing. Includes, templates, remote +inheritance, Lua and shell code are not executed. See the +[Theme Bridge reference](/docs/reference/theme-bridge/) for exact formats, limits, +ownership rules and removal behavior. diff --git a/website/src/content/guides/transcript.md b/website/src/content/guides/transcript.md new file mode 100644 index 00000000..b829115d --- /dev/null +++ b/website/src/content/guides/transcript.md @@ -0,0 +1,92 @@ +--- +title: Transcript and history +description: Read command blocks, search output and keep copying plain. +group: Daily work +order: 6 +sources: [README.md, ARCHITECTURE.md, docs/privacy.md] +--- + +## Output organized around commands + +The transcript keeps submitted commands with status, timing and their output. +Folding helps manage long output without changing what the underlying program +wrote. `/transcript` controls transcript presentation. + +| Action | Purpose | +| --- | --- | +| `/find` | Search the transcript | +| `/filter` | Filter the displayed transcript | +| `/copy` | Navigate completed outputs, then Enter to copy | +| `/copy latest` | Copy the latest output immediately | +| `/copy ui` | Search and select multiple outputs | +| `/actions` | Navigate completed output and open its contextual actions | +| `/compare` | Compare the latest output with its previous run | +| `/history` | Browse command history | +| `/clear` | Clear through NMSh's own action | + +Use `/help` for the arguments supported by your installed version rather than +assuming another terminal's shortcut or slash-command syntax applies. + +## Select output, then choose what to do + +New configurations use output navigation for bare `/copy`: Up and Down move between +completed shell outputs, Enter copies and Esc restores your view. An explicitly saved +Quick Copy or Interactive Picker preference is preserved. `/copy latest` always takes +the direct path, regardless of that preference. `/copy settings` changes the behavior. + +`/actions` uses the same navigation, with Enter opening the selected output's Actions +menu. `/actions N` targets a numbered previous output directly. Actions are checked +again against the live record, so a cleared or replaced record cannot silently +redirect an operation to neighboring output. + +The separate `/copy ui` picker supports search, preview and multiple selection. +Space selects, `a` selects all, `/` searches, `p` previews and Enter copies. Plain +copy includes stored folded lines and no NMSh styling. Status text and automatic +expansion start Off; choosing a smaller display is not choosing a smaller clipboard. + +## Reports and comparisons + +Reports offer exact-text review and best-effort redaction before sharing. That is +different from plain copy, which is deliberately **not redacted**. Inspect both the +command and output for secrets. Compare's clipboard diff is also not redacted. + +`/compare`, `/compare N` and `/compare N M` select outputs using the same numbering +as `/copy`. Background task output and queue summaries are not ordinary shell output +candidates. See [Queue and background tasks](/docs/execution/) for their separate paths. + +## Use a result as the next step + +A block's Actions can recognize references such as files, links, commits and device +names. Choose to open, copy or stage one explicitly; recognizing it does not execute +anything. Review the destination or staged command, especially for untrusted output. + +F8 and Shift+F8 move between failed commands using their exit status, not guesses +from error-looking text, and preserve your draft. The command palette offers the +same Previous/Next failed command actions if your terminal intercepts those keys. + +## Follow versus inspect + +While a command runs, FOLLOW keeps the viewport near the newest output. Scrolling +into history enters DETACHED inspection instead of constantly pulling the view +away from what you are reading. Flow composer return behavior is documented under +[Composer and input](/docs/composer/). + +The terminal host still owns ordinary terminal text selection. Mouse support +depends on host reporting; PageUp/PageDown is the fallback for transcript scrolling. + +## History is not the same as a transcript + +Command history supports recalling input. A transcript records command text and +output for later reading. A live session includes the still-running shell. These +surfaces have different retention and lifecycle behavior. + +Transcripts can contain secrets printed by your commands. Local-first does not +mean automatically redacted. Review text before copying or sharing it, and read +[Privacy and local data](/docs/reference/privacy/) before moving session files. + +## Known output limits + +Interactive applications can switch to passthrough rather than remain embedded +in the transcript. Erase sequences and inline redraws remain compatibility-sensitive; +check the [changelog](/docs/reference/changelog/) and the +[physical QA record](/docs/reference/physical-qa/) for what has been verified. diff --git a/website/src/content/guides/troubleshooting.md b/website/src/content/guides/troubleshooting.md new file mode 100644 index 00000000..f9315d43 --- /dev/null +++ b/website/src/content/guides/troubleshooting.md @@ -0,0 +1,65 @@ +--- +title: Troubleshooting +description: Diagnose installation, keyboard, prompt, clipboard and session problems. +group: Understand the system +order: 15 +sources: [README.md, SUPPORT.md, SECURITY.md, docs/architecture/platforms.md] +--- + +## Start with facts + +```sh +nmsh --version +nmsh doctor +``` + +Include the installed version, shell, operating system, terminal host and a minimal +reproduction in a bug report. Diagnostics are designed to omit history, prompts +and secrets, but review anything you share, especially additional command output. + +## NMSh does not start + +Check Node.js 22+ and a supported shell. A source build needs the `node-pty` native +install step; refusing its install script prevents a working PTY. Build failures +can require the platform's C/C++ toolchain and Python. macOS Bash 3.2 is too old: +use zsh or install a newer supported Bash. + +GUI terminals can have a different PATH from an interactive shell. Use absolute +Node/launcher paths in terminal launch configuration as described on the +[installation page](/install/#troubleshooting). + +## Keys, glyphs or prompt look wrong + +Try Ctrl+J for a newline, safe glyphs for font issues and reduced motion for visual +comfort. Confirm the terminal forwards your keybinding. Do not restore ZLE UI to +fix a composer shortcut. External prompt providers have documented limitations; +try Native or None to isolate provider-specific behavior. + +## Clipboard or notifications are unavailable + +On Linux, check `wl-copy` under Wayland or `xclip`/`xsel` under X11. A headless +environment may not have a clipboard or D-Bus desktop notification service. +This is a host capability issue, not evidence that `/copy` should add ANSI chrome. + +## A session still runs after I close a window + +That is normal for a service-backed live session. List it with `nmsh --sessions` +and attach explicitly. Do not uninstall while work is still running. Saved +transcripts and live processes are different; see [Sessions](/docs/sessions/). + +## Known release limitations + +Physical-terminal evidence for v0.20.0 is partial: the first-run setup and Tools flow +were accepted on macOS, and no physical Linux or WSL pass has been made. Fish type-ahead +during `read` remains a tracked compatibility concern. Managed targets are Claude +Code beta only; the GitHub workspace is read-only and workspace regions are +experimental, Off by default. Background tasks need a matching session service. +Check the [changelog](/docs/reference/changelog/) and +[release notes](/releases/) for the full list of known limitations. + +## Ask for help safely + +[Open a bug report](https://github.com/raiseCatError/notMyShell/issues/new?template=bug_report.yml) +for reproducible problems. **Do not disclose a suspected vulnerability publicly.** +Use the repository's [private security reporting](https://github.com/raiseCatError/notMyShell/security/advisories/new) +and [security policy](/docs/reference/security/) instead. diff --git a/website/src/content/reference/accessibility.md b/website/src/content/reference/accessibility.md new file mode 100644 index 00000000..afbcbc8f --- /dev/null +++ b/website/src/content/reference/accessibility.md @@ -0,0 +1,48 @@ +--- +title: "Accessibility baseline" +description: "Canonical accessibility baseline from the v0.20.0 published source." +group: Technical reference +order: 110 +source: "docs/accessibility/baseline.md" +--- + +This applies to NMSh-owned surfaces only: panels, the composer, status rows, help and the transcript chrome. Raw PTY output, archived command output and `/copy` are never restyled. + +Accessibility flows through the shared primitives (`ui/palette`, `ui/glyphs`, `ui/actions`, `ui/formControls`, `presentation/environment`), not a separate renderer. + +## Controls + +| Setting | Effect | +| --- | --- | +| `NO_COLOR=1` (non-empty) or `TERM=dumb` | `foreground()`/`background()` emit nothing. Bold, inverse and glyphs remain. | +| `NMSH_COLOR=none` / `256` / `truecolor` | Explicit override, wins over `NO_COLOR`/`TERM`. `256` maps NMSh colors to the xterm 256 palette. | +| `NMSH_ICONS=safe` | ASCII-safe glyph set (existing). | +| `NMSH_REDUCED_MOTION=1` | Shimmer/activity glyph phase and the welcome blink stay still. Durations keep counting. Deterministic presentation implies it. | + +These are environment controls. A persisted setting would change the public configuration schema and is left to the Settings work. + +## Acceptance criteria + +1. Every action is reachable by keyboard; the pointer only adds shortcuts (`ui/actions`). +2. The focused row is identifiable without color: a pointer glyph (`›`, or `>` in safe mode) or `>` in plain form controls. +3. Changed and error states are text (`(changed)`, `Error: ...`), not color alone (`ui/formControls`). +4. Success/failure rows carry distinct glyphs (`✔`/`✘`, or `+`/`x` in safe mode) in addition to color. +5. With `NO_COLOR`, no NMSh-generated color escape is written; layout is unchanged. +6. With reduced motion, no NMSh-owned decoration changes between frames unless state changes. +7. No width-changing animation. + +## Audit result + +- Met: keyboard operation in palette, Settings, draft panels; pointer-free navigation; safe glyph set; status glyphs; footer help derived from actions. +- Known gaps: command-row background bands (`TranscriptPresenter`) are a color-only cue for "this is a command" under `NO_COLOR`; the prompt prefix is the remaining cue. Provider panels (prompt, welcome, suggestions) keep hand-written footers. +- Not supported: automatic 256-color detection (only explicit `NMSH_COLOR=256`), 16-color output, and a persisted reduced-motion setting. + +## Screen readers + +NMSh redraws full-screen frames in the alternate screen. Terminals expose that to assistive technology inconsistently, so NMSh cannot guarantee screen-reader-friendly output. What it does provide is text-carried state and no pointer requirement; behavior with a specific terminal and reader has not been verified. + +## Motion + +`motion/motion.ts` is a pure engine: profiles per semantic state (`waiting`, `processing`, `streaming`, `transition`, `completion`, `failure`) sampled at an elapsed time. Reduced motion holds every profile still. It never schedules timers, changes width or touches PTY output. + +The shimmer (`status/shimmer.ts`) samples the `processing`/`streaming` profiles. The one existing activity timer is already bounded (100 ms), cleared on exit, and its renders are suppressed during passthrough, so no shared scheduler was added; one should arrive with a second animated consumer. `waiting`, `transition`, `completion` and `failure` have no consumer yet. diff --git a/website/src/content/reference/architecture.md b/website/src/content/reference/architecture.md new file mode 100644 index 00000000..82d66b3d --- /dev/null +++ b/website/src/content/reference/architecture.md @@ -0,0 +1,411 @@ +--- +title: "Runtime architecture" +description: "Canonical runtime architecture from the v0.20.0 published source." +group: Technical reference +order: 100 +source: "ARCHITECTURE.md" +--- + +## 1. The 30-second explanation + +notMyShell (NMSh) is a terminal frontend. It is not a shell: your real zsh, Bash, or Fish still interprets commands and runs programs. It is not a terminal emulator: Ghostty, Terminal.app, Kitty, or another terminal application still draws the window, fonts, and terminal cells. + +NMSh sits between them and owns the interaction around commands: the composer, suggestions, history presentation, command feedback, session navigation, panels, themes, and integrations. + +```text +Terminal emulator: window, fonts, terminal protocols + ↕ +NMSh frontend: editor, transcript, panels, presentation + ↕ session client +Session owner: usually a separate local NMSh service + ↕ pseudo-terminal (PTY) + Real zsh / Bash / Fish + ↕ + Programs / OS +``` + +The shell stays alive between commands. `cd`, exported variables, aliases, functions, and jobs therefore belong to a real continuing shell, rather than to a simulation assembled by NMSh. This document describes the current implementation, including development features; it is not a statement that every feature described has been released. + +## 2. What NMSh is made from + +The application is **TypeScript running on Node.js**, built into JavaScript for its command-line launcher. Node supplies process management, streams, filesystem access, events, and local sockets. TypeScript makes the contracts between shell events, settings, panels, and actions explicit. + +**node-pty** creates a pseudo-terminal: a communication channel that looks like a terminal to the shell and its programs. Ordinary pipes would not provide the same interactive behavior, terminal sizing, and job control. + +The frontend draws using **ANSI and related terminal escape sequences**. These are byte instructions for colors, cursor movement, screen modes, hyperlinks, and input reporting. NMSh has its own renderer and screen planner rather than a general web UI framework. **string-width** measures terminal cell widths so wide characters and decorative glyphs do not break wrapping and cursor placement. + +Small **shell bootstrap scripts** load the user's normal shell environment, suppress competing shell prompt/editor UI, and report command lifecycle events. Local JSON stores settings and transcripts. **smol-toml**, **yaml**, and **fast-xml-parser** support bounded data imports and reviewed configuration formats; they do not make executable configuration safe to run. + +The main coordinator is `TerminalApp`. It connects input, shell events, output state, panels, settings, and rendering. The editor, shell transport, transcript, and domain controllers provide more focused responsibilities around that coordinator. + +## 3. The terminal + shell relationship + +The outer terminal translates physical keys into bytes and displays bytes NMSh writes. NMSh reads those keys and usually edits its own command buffer. The real shell runs inside an inner PTY. + +The object that owns that PTY is `ShellSession`. It launches one persistent interactive shell, writes submissions, forwards interrupts, resizes the PTY, and separates authenticated lifecycle messages from program output. Normally a separate `SessionService` owns `ShellSession`; an in-process fallback can own it inside the frontend. + +```text +Normal command: +keys → NMSh editor → shell PTY → program +screen ← NMSh transcript ← PTY output + +Interactive/fullscreen program: +keys ─────────────────→ shell PTY → program +screen ←─────────────── program's terminal output + NMSh presentation suspended +``` + +The shell remains authoritative for parsing, expansion, pipes, redirections, working directory, and jobs. NMSh owns the editable text and its presentation. It does not turn each command into a separate Node subprocess. + +For fullscreen and interactive programs, NMSh switches to **passthrough**: it suspends its screen, gives the program the full terminal dimensions, and forwards input and output. A known-command heuristic can select this immediately; output classification can also detect terminal behavior that requires it. Terminal modes are tracked so attachment and return to the composer can restore the appropriate state. + +Typing `caffeinate -i` invokes a program through the shell and occupies that shell until it finishes. `/zoomies` invokes NMSh's separate Keep Awake controller. It can keep an OS assertion alive without occupying the managed shell. + +## 4. What happens when I type a command? + +Consider `npm test`: + +1. **Keystrokes arrive.** The key decoder turns terminal bytes into editing actions. NMSh's `CommandEditor` keeps the command text and cursor position. +2. **Presentation updates.** A synchronous lexical highlighter identifies words, strings, operators, and other tokens. Background command classification supplies facts such as whether `npm` is an executable, alias, or builtin. Suggestions and completion services offer candidates without executing the unfinished command. +3. **Enter submits.** `TerminalApp` reads the actual editor text and checks for an NMSh slash action. For a shell command, it creates a command block, captures historical context, starts activity feedback, and sends the text through the session client to `ShellSession`. +4. **The real shell executes.** Its bootstrap reports a command-start marker. The shell resolves `npm`, performs its own parsing, and launches the program in its continuing environment. +5. **Output returns through the PTY.** Shell protocol decoding removes NMSh lifecycle messages. Program output flows into the output buffer, where terminal formatting and progress updates are represented for the transcript. A session service also retains sequenced events for recovery and reattachment. +6. **The shell becomes ready again.** A marker reports exit status and working directory. NMSh completes the block, records elapsed time, updates history and context, and refreshes the prompt and suggestions. + +```text +key → editor → highlight/suggestions → submit → session client + → persistent shell → program → PTY output → transcript + → shell readiness marker → completed block + next prompt +``` + +The command's displayed colors never become part of the submitted text. A command can have rich NMSh presentation while the shell receives ordinary command text. + +## 5. The composer + +The **composer** is the whole input area: editable text, optional prompt/context, suggestions, and surrounding edges. The editor inside it supports multiline input and owns editing rather than delegating to the shell's line editor. + +The composer can be docked at **Bottom**, docked at **Top**, or placed in **Flow** after the newest transcript output. A one-line layout places context alongside input; a two-line layout separates them. Long input still wraps and can contain explicit newlines. Prompt None currently uses the one-line geometry. + +Prompt/context describes the shell and project; editable input is the command being composed. Dividers and borders are presentation around those regions. Accessories such as Keep Awake can occupy an available edge, an adjacent row, or trailing input space according to the screen plan. They do not become prompt-provider content or editable text. + +Each frame has one geometry plan, produced by `src/app/screenPlan.ts`. Rendering, cursor placement, wrapping, mouse hit testing, viewport height, panel placement, and PTY sizing use this shared plan. That prevents a border or accessory from shifting the cursor while another subsystem still assumes the old coordinates. + +## 6. Prompts and providers + +The prompt supplies context; NMSh supplies the composer that contains it. Current choices are: + +| Choice | Where context comes from | +|---|---| +| NMSh Native | NMSh modules for directory, project, Git, toolchains, and other context | +| None | No prompt/context content | +| Starship | A bounded invocation of the installed Starship executable | +| Powerlevel10k | An isolated zsh helper loading the installed theme and its configuration | +| Oh My Posh | A bounded invocation of the installed Oh My Posh executable | + +External renderers receive shell context such as directory and exit status. Their output is parsed into prompt content that NMSh lays out. They do not receive ownership of the live shell's editor. Powerlevel10k's helper deliberately disables ZLE, zsh's interactive line editor, so it cannot compete with NMSh. + +These helpers have time/output limits and use pipes rather than attaching their UI to the host terminal. They are still trust boundaries: running an installed prompt program or loading Powerlevel10k configuration can execute that provider's code. + +### Native modules and the Context Engine + +Native context follows a shared pipeline: + +```text +shell prompt / editor demand / refresh / command end + → Context Engine: trusted core capabilities, scheduled on demand + → immutable facts → native and declarative (pack) modules + → Surface Router → Main Prompt / Right Context / Context Rail / Status Strip + → semantic segment painter → shared screen plan +``` + +Capabilities own collection and its safety policy: what they read, which allowlisted environment variables they may see (as the live shell reports them each prompt), cost, timeout, cache lifetime and the privacy class of each field. The engine resolves only what visible modules demand, off the typing path, with bounded concurrency, timeouts and backoff, cancellation on directory change, staged scopes and stale-while-revalidate caching. Facts describe values with provenance, freshness, trust and privacy metadata. Modules consume resolved values; rendering and routing do not perform discovery or launch probes. The legacy shell-context snapshot is adapted into the same fact model, so providers, history and Rich Git keep working. + +The Main Prompt carries identity and navigation context. Right Context is a separately anchored prompt area. The Context Rail is live native context attached to the composer, vertically or Right of Prompt; its position does not turn it into Right Context. Width fitting preserves the independent right-context anchor and drops Rail content by priority when space is scarce. Rail rows are frontend geometry, not transcript output, and the live Rail is not archived into command history. + +**Context Packs are data.** A pack (`nmsh.context-pack/v1`, bounded and strictly parsed) names capabilities core implements and describes modules over them; it cannot carry commands, code, paths, templates or hooks, and gains no authority by being installed. First-party modules (project, runtimes, environment managers, infrastructure, cloud, system, Git extras, Claude Code agent context) ship as bundled packs; `nmsh packs` installs local ones atomically with integrity checks. Recommendations explain local evidence and never install or enable anything. These shipped in v0.18.0. See [Context Modules](docs/architecture/context-modules.md) and the [Context Engine design](docs/design/context-engine.md). + +**Prompt None can still show a composer marker.** The marker identifies where input starts and belongs to NMSh's editor presentation. Removing context does not remove the editor's own marker or accessories. + +## 7. Output and transcript + +NMSh separates **program output** from **NMSh-authored presentation**. The output buffer, `OutputBuffer`, holds command records and parsed output lines. A transcript presenter decides how those records appear: command headers, historical context, feedback, folding, and spacing. + +Raw output is not blindly printed over the composer. `AnsiOutputParser` interprets a bounded subset of terminal behavior, including colors, carriage returns, backspaces, and hyperlinks, to retain a usable representation. Classification can choose transcript presentation, progress handling, or passthrough. This is not semantic recoloring of arbitrary stdout: the program's text and color choices remain its own. + +NMSh-authored submitted command lines can keep semantic highlighting. Folding changes which output rows are visible; it does not rerun commands or turn hidden output into a different result. Search, selection, and copy work from transcript state. `/copy` exports plain text without NMSh's ANSI chrome. + +The Status Strip is one frontend row at the top or bottom edge of the NMSh pane, planned by the same screen plan as everything else, with left, center and right groups fitted in display cells. Its clock, CPU, RAM, battery and uptime are Context Engine facts demanded only while the row shows; strip-routed modules are the same facts every surface uses. Painting it reads no files and starts no process. Passthrough hides it and panels own their screen as before. + +Historical command blocks retain prompt/context snapshots from submission time. Viewing an old command should not pretend it ran in today's directory or Git state. Frontend chrome such as the Status Strip, find bar, and accessory rows stays outside the transcript. + +The viewport follows the newest output during execution (**FOLLOW**). Scrolling into history enters **DETACHED** viewport mode so new output does not pull the view away. This is a viewing state, distinct from detaching a live shell session. + +## 8. Sessions + +A **live session** is a running shell and PTY. A **saved transcript** is a record of what happened. Those have different lifetimes. + +Normally the frontend connects to a per-user local service, starting it on demand. The service owns shells independently of terminal windows, permits one controlling frontend per session, and keeps running when that frontend detaches or disappears. + +```text +create → attached ⇄ detached → shell exits / explicit termination + │ │ │ + └──── transcript journal ────────┘ + ↓ + saved transcript +``` + +Reattaching a live session reconnects to the same shell: aliases, variables, jobs, and current program remain there. The frontend restores its journal and replays later service events. Restoring an archived transcript through `/resume` replaces this window's presentation, archiving its current view first, while retaining its current live shell. It cannot resurrect the archived shell's environment or jobs. + +Retention is bounded. Unacknowledged stream events begin in memory and spill to a JSON-lines spool. Once a frontend has durably journaled events, it acknowledges their sequence numbers. Output beyond spool limits can be dropped, with truncation reported on replay. + +If the service cannot be established before session creation is confirmed, NMSh falls back to an in-process shell with a notice. That shell cannot survive frontend exit or be detached and reattached. A service crash or OS restart also does not magically preserve live processes. + +Session status combines authenticated shell lifecycle, observed terminal modes, activity emitted by programs, and the PTY's foreground-process name when available. Silence does not prove completion or a need for attention. Unknown facts remain unknown rather than being guessed from an animation or command name. + +## 9. ShellAdapter + +Shell-specific behavior lives behind a common boundary called `ShellAdapter`. Its three implementations support **zsh**, **Bash 4.4 or newer**, and **Fish**. All launch real persistent interactive shells and report the same lifecycle grammar. + +Adapters own executable selection, bootstrap files, environment setup, builtins, history parsing, and completion sources. The composer, transcript, session transport, and panels remain shared. Capabilities describe real differences: Bash completion does not provide Fish-style descriptions, for example. + +The zsh adapter uses a private bootstrap directory and disables ZLE. Bash uses a private rcfile and disables Readline editing. Fish retains its editor internally, so NMSh suppresses its prompt-to-command redraw bytes and answers necessary terminal queries. Consequently, Fish messages printed while idle at its prompt can also be suppressed. + +The interactive shell loads the user's startup configuration as part of normal shell startup. NMSh does not rewrite those files to establish its UI. Startup output is bounded and sanitized; typed input waits for readiness so it cannot accidentally answer a `read` in a startup file. Bootstrap also suppresses automatic Fastfetch startup. + +Command classification uses an isolated zsh helper, `SemanticService`, or live-name snapshots plus PATH classification for Bash/Fish. Completion helpers are separate from the execution PTY. They query trusted shell facilities without executing the partial command as a submission. + +Switching backend replaces the shell while keeping NMSh session identity, history, draft, and settings. The current implementation archives the old presentation and starts a fresh view for the new backend. It does not migrate aliases, variables, or jobs. Switching is refused while execution, startup, or known background/stopped jobs make replacement unsafe. + +## 10. Slash commands and NMSh actions + +`npm test` goes to the real shell. Recognized slash commands such as `/theme`, `/tools`, and `/zoomies` are parsed into **NMSh actions** and dispatched inside the frontend. + +These actions open panels, update settings, navigate sessions, or invoke a domain controller. They are not aliases installed in the user's shell. The slash parser returns structured action kinds instead of treating every action as a shell string. Unknown single-line slash input currently reports an unknown NMSh command rather than executing it. + +Panels are control surfaces for NMSh state and supported integrations. Where an action changes external files or installs software, its controller builds a specific reviewed operation rather than handing panel text to a shell. + +## 11. notMyUI / panel system + +**notMyUI** is the shared terminal UI vocabulary used by NMSh panels. It is implemented in this repository, primarily under `src/ui/`, rather than as an independent application framework. + +Shared primitives handle panel frames, tabs, grouped lists, selected rows, controls, colors, glyphs, and focus presentation. Domain panels hold their own state and turn keyboard or mouse interaction into actions; the coordinator dispatches those actions and integrates the panel with screen geometry. + +Rows identify selectable settings or commands. Focus determines whether navigation operates on a list, tabs, or an editing control. Settings and the command palette reuse these ideas, making different features feel related without forcing every panel into an identical widget implementation. + +## 12. Themes and appearance + +A **semantic palette** names colors by purpose: primary text, subtle text, success, warning, failure, selection, syntax roles, and so on. Native themes map those roles to actual colors. Renderers can then agree on what a warning means without each choosing a hardcoded color. + +**Theme Studio** creates, edits, imports, duplicates, and exports NMSh themes. Imported colors become normal local theme assets with stable IDs and source metadata. They do not require the original application or source file to remain installed. Imports parse supported formats as data rather than executing Lua, templates, shell snippets, or includes. + +**Chroma** adds decorative treatments, gradients, and motion to eligible NMSh surfaces. It is a presentation layer, not a replacement for the palette's underlying meaning. It does not enter generated external artifacts. + +Glyph modes choose between richer symbols and more portable alternatives. Color capability detection, `NO_COLOR`, and reduced-motion settings degrade presentation without changing command behavior. Cursor presentation, idle visuals, and UI chrome have their own settings around the shared appearance system. + +Theme Studio controls NMSh's appearance. **Theme Bridge** is the separate mechanism for extending selected colors to external tools. + +## 13. Theme Bridge + +Choosing an NMSh theme does not automatically rewrite application configuration. Theme Bridge is an explicit, initially disabled system with known target adapters. + +Its global policy can **Follow NMSh**, **Choose theme** to pin a theme, or **Manual** to use each target's saved choice. Per-target choices are Independent, Follow NMSh, and Choose theme. Global policy changes preserve the Manual choices for later restoration. + +| Current target | How colors reach it | +|---|---| +| fzf | NMSh-controlled invocation options | +| less / man | Allowlisted session environment values | +| File listing colors | GNU/BSD color variables and controlled listing behavior | +| bat | Generated theme file, reviewed cache setup, and theme environment selection | +| tmux | Generated managed configuration/colors and reviewed activation | +| Neovim / Vim | Generated colorschemes and reviewed activation hooks | +| Helix | Generated theme in its themes directory and reviewed selection | +| delta | Never managed (NMSh does not change git config); its syntax highlighting follows bat's theme through `BAT_THEME` unless git config pins it | + +Environment changes reach a persistent shell through one **environment sink**: generated files for zsh, Bash, and Fish, applied by the adapter's prompt hook. Their grammar permits allowlisted variables and quoted literals. Values take effect at the next prompt. Clearing a value restores what NMSh replaced only while the shell still contains NMSh's value. + +Generated files and inserted activation lines are recorded in an **ownership ledger**. Content hashes prove whether a file still matches what NMSh wrote. A user-edited or unrecognized artifact causes a conflict instead of being overwritten. Activation in executable configuration is an exact reviewed edit, not a generic merge of shell, Lua, or Vimscript. bat and Helix need artifacts in their own theme directories; many other artifacts live under NMSh's Theme Bridge directory. + +## 14. Tools, providers and integrations + +These terms describe different relationships: + +| Term | Meaning in NMSh | +|---|---| +| Tool | A curated external utility or detected shell-environment component | +| Provider | A selected implementation of a frontend role, such as prompt, history, picker, suggestions, or navigation | +| Integration | A supported connection to an external system, including health and ownership information | +| Configurable tool | A tool with a reviewed configuration adapter; detection alone is insufficient | +| Shell framework | Shell code/plugins loaded by startup configuration, such as Oh My Zsh or Prezto | +| Package-manager infrastructure | Software such as Homebrew that installs and manages other packages | + +`src/tools/catalog.ts` is the central curated tool registry. It records detection, categories, installation recipes, and relevant capabilities. Frameworks can be detected from filesystem evidence even when they have no executable on PATH. Detection grants no automatic installation or write authority. + +The provider-family registry connects role choices to persisted settings. The tool-configuration registry separately determines what `/configure` may manage. `/integrations` presents integration readiness and repair/setup actions, including Theme Bridge health. + +Homebrew is package-manager infrastructure: NMSh uses it for known install recipes and package facts. It is not simply another interchangeable prompt or utility provider. Special installers and framework setup have explicit flows and boundaries rather than being treated as ordinary package names. + +## 15. Dotfiles + +The dotfiles surface discovers recognized configuration in a local directory or checkout, including plain, Git, GNU Stow-style, and chezmoi layouts. An explicit remote flow can obtain a checkout before scanning. Repository content remains untrusted data. + +Discovery is bounded by entry count, depth, and file size. Recognition uses the tool-configuration registry. Symlinks are shown without being followed; scripts and templates are identified without being run or rendered. + +The review plan distinguishes supported field imports, exact copies, and inspect-only files. tmux imports supported options and bindings into NMSh's own model. Exact copying requires a registry-specific validator: being valid TOML or JSON alone is insufficient. Executable configurations remain inspect-only in this workflow. + +Changes are reviewed before application. Copy plans check whether the destination changed since review and back up existing content. NMSh does not execute repository installers, hooks, Make targets, Stow operations, chezmoi scripts, or arbitrary configuration code to discover what a repository means. + +## 16. Keep Awake + +`/caffeinate`, `/awake`, and `/zoomies` are three names for one NMSh-owned feature. A controller creates an OS power assertion in a detached background process, independently of the shell PTY. + +Backends use Apple's fixed-path `caffeinate` on macOS, `systemd-inhibit` around an NMSh wait helper on supported Linux systems, and an execution-state helper on Windows when its prerequisite is available. Capabilities differ: the Linux backend does not claim display inhibition. This backend's Windows code does not imply native Windows shell/frontend support. + +NMSh records the PID, launch arguments, ownership token where applicable, mode, and timing. Before stopping an assertion it verifies that the live process matches its owned record, rather than killing any process named `caffeinate` or trusting a reused PID. Stop sends termination to that verified process and only escalates while ownership still matches. + +The assertion can outlive an NMSh window. Duration and explicit stop control its lifetime. Presentation can use a composer accessory, Status Strip, idle reminder, or screensaver state; those are views of the controller's state, not text sent to the shell. + +## 17. Host / terminal integration + +Terminal integration uses optional escape-sequence protocols. **OSC** means Operating System Command, a terminal protocol family; these messages are not shell commands. + +- **OSC 7** tells a host the current directory, helping it open new tabs or panes in the right place. +- **OSC 8** attaches hyperlink targets to displayed text. +- **OSC 133** marks prompt, input, command-start, and command-end zones for cooperating terminals or multiplexers. +- **Private OSC 777 NMSh messages** carry shell readiness and execution events with a per-session token. NMSh validates that token before treating output as lifecycle evidence. + +- **OSC 0/2** sets the window title (opt-in: Off, Project, Project and session) only while NMSh owns the screen; programs own the title while they run. + +Public host markers are derived from NMSh's authenticated lifecycle. NMSh does not depend on receiving them back. It writes command start (`C`) at once, holds prompt-time markers while a fullscreen program owns the screen, re-reports the directory after one did, and replays no history on reattach. + +Capability detection combines passive host profiles and active probes. Ghostty, Kitty, WezTerm, iTerm2, and Windows Terminal have specific hints; Terminal.app receives conservative baseline behavior plus directory signaling. Windows Terminal detection can describe a WSL host without implying native Windows execution support. + +Keyboard, mouse, synchronized drawing, hyperlinks, colors, and graphics degrade independently. Direct preference/keyboard configuration integration is currently Ghostty-specific. tmux and other multiplexers hide outer-host hints, so NMSh does not assume that an outer terminal's graphics or keyboard features work unchanged inside a pane. + +## 18. tmux + +tmux is an independent multiplexer that owns sessions, windows, panes, borders, and its status line. NMSh can run inside a pane, or a shell command can launch tmux through passthrough. + +Theme Bridge supplies colors. **Config Studio**, opened through `/tmux`, manages supported options, keys, a status layout, and an optional pane frontend. NMSh stores a typed model and generates one managed configuration file that combines its settings and applicable Bridge colors. The user's config includes that file through a reviewed activation step. + +The optional **NMSh pane frontend** changes tmux's `default-command` for new panes/windows without an explicit command. It does not change `default-shell`, which still identifies the shell tmux uses to launch commands. Existing panes retain their current processes. The generated launcher guards against recursively starting NMSh inside NMSh's managed shell. + +The prompt inside a pane belongs to the program running there; tmux's status appearance is a different owner. Config Studio imports only supported literal settings and bindings, leaving dynamic commands and unsupported configuration outside its model. + +## 19. Ask + +`/btw` opens **Ask NMSh**; `/ask` remains a compatibility alias for the same feature. Ask resolves plain-English requests into NMSh's existing capabilities. Its first path is deterministic: intents, command knowledge, project facts, and supported actions work without a model. + +Results are typed answers, proposals, choices, unsupported requests, or refusals. Actions identify specific operations such as opening a panel, attaching a session, changing a setting, running a fixed command, or applying a verified edit plan. Existing controllers perform those operations. + +Optional **local understanding** can help interpret requests when enabled. Auto uses a model when deterministic interpretation is unclear or ambiguous; Always prefers model assistance while still using deterministic action builders. A separate local model service manages runtime work. Model assistance chooses from a bounded capability inventory and produces validated structured interpretations. NMSh then resolves those interpretations against known facts and its deterministic policies; a failed model leaves the deterministic result available. + +Model text is not directly executed as a shell command or filesystem operation. Configuration, mutation, and installation require confirmation of the exact action in Ask; destructive suggestions are Copy/Insert only. Copying or inserting a suggested command does not execute it. File plans and supported configuration still use their normal path, hash, and ownership checks. + +## 20. Configuration and persistence + +Let `` mean the directory selected by the shared path resolver: + +- An absolute `$XDG_CONFIG_HOME/nmsh` when `XDG_CONFIG_HOME` is set. +- Otherwise `~/Library/Application Support/notMyShell` on macOS. +- Otherwise `~/.config/nmsh`. + +| State | Location / owner | +|---|---| +| Settings, provider choices, theme assets, Bridge policy | `/config.json` | +| Saved transcripts and summary metadata | `/sessions/` | +| Bridge environment files and ownership ledger | `/theme-bridge/` | +| Most generated Bridge artifacts | Subdirectories of `theme-bridge/`; bat/Helix themes use their tool directories | +| tmux's typed configuration model | `/tools/tmux.json` | +| Keep Awake process record | `/keep-awake.json` | +| Live service sockets and stream spools | Private runtime directory, separate from configuration | + +On Linux, a verified private `$XDG_RUNTIME_DIR/nmsh` is preferred for live runtime data. Otherwise NMSh uses a private per-user temporary directory; `NMSH_RUNTIME_DIR` can override it. Socket names distinguish protocol versions. Live shell environment stays in memory, not in the transcript store as a recoverable shell image. + +Configuration loading normalizes data, supplies defaults, and migrates older shapes. Theme-library normalization maintains stable references and a compatibility mirror of the active custom theme. Saving settings uses staged replacement and preserves unrelated fields; unreadable or malformed existing settings are refused for saving rather than silently overwritten. + +## 21. Security boundaries + +The central distinction is between **data NMSh can validate** and **code an external system executes**. Normal interactive shell startup and explicitly selected providers execute trusted user-installed code. Import and discovery workflows do not receive that same authority. + +Entering a repository must never itself execute arbitrary repository-controlled code through NMSh context discovery. Context metadata is read as bounded data; executables found on PATH are never run for context (versions come from install layouts), and only fixed system tools and trusted Git are spawned. Context Packs are declarative and grant no authority. Normal user-configured shell startup and explicitly chosen external providers remain separate trust boundaries. + +Theme imports use bounded parsers without templates, includes, or code execution. Arbitrary configuration is not generically rewritten. Supported installs and helper operations use fixed executables and structured argument arrays rather than interpolating requests into shell strings. Helpers have time/output bounds appropriate to their role. + +Generated-file hashes and ownership records protect external artifacts; process verification protects stop/kill operations. A discovered file, tool, or PID is not proof that NMSh owns it. Raw PTY output retains program formatting through a controlled presentation path, and unauthenticated output cannot complete an NMSh command by impersonating its private protocol. + +These boundaries do not make arbitrary shell commands or providers harmless. They constrain NMSh's own authority. See [SECURITY.md](SECURITY.md) for the security policy and detailed trust boundaries. + +## 22. Repository map + +| If you want to understand… | Start here | +|---|---| +| Startup and frontend orchestration | `src/index.ts`, `src/app/` | +| Screen layout and terminal drawing | `src/app/screenPlan.ts`, `src/terminal/` | +| Editing, highlighting, suggestions | `src/input/`, `src/suggestions/` | +| PTY, adapters, shell knowledge and completion | `src/shell/` | +| Live service, sockets, replay and attachment | `src/session/` | +| Saved transcripts and resume UI | `src/sessions/` | +| Output parsing, folding, selection and presentation | `src/output/` | +| Context Engine, capabilities, facts, packs and surface routing | `src/context/` (`engine.ts`, `capabilities/`, `packs/`), `src/prompt/configuration.ts`, `src/prompt/ModulesPanel.ts`, `src/prompt/railLayout.ts` | +| Native/external prompts and shared settings | `src/prompt/`, `src/configuration/` | +| Panels and shared UI primitives | `src/ui/` plus feature-specific panel modules | +| Theme Studio, palettes and decoration | `src/appearance/`, `src/chroma/`, `src/motion/`, `src/cursor/`, `src/idle/` | +| External colors and ownership | `src/themeBridge/` | +| Curated tools, configuration and packages | `src/tools/`, `src/providers/`, `src/packages/` | +| Dotfiles and Keep Awake | `src/dotfiles/`, `src/keepAwake/` | +| Slash actions, Ask and optional local models | `src/commands/`, `src/ask/`, `src/understanding/` | +| Host capabilities and passthrough policy | `src/host/`, `src/presentation/`, `src/passthrough/` | +| Agent-session integration, the Claude Code status-line bridge and managed tasks | `src/agents/`, `src/cli/agentStatus.ts`, `src/tasks/` | +| Automated behavior evidence | `tests/`, verification scripts in `scripts/` | + +## 23. A few end-to-end examples + +### Running `git status` + +`editor text → semantic command presentation → submission block → session client → real shell → Git → PTY output → parsed transcript → readiness/status marker → completed block and refreshed context` + +Git chooses its output; NMSh supplies the surrounding command history and feedback. + +### Opening `/theme` + +`slash parser → Theme Studio state → shared panel primitives + screen plan → edit/select local theme → normalized settings save → NMSh surfaces repaint` + +An enabled Follow NMSh Bridge policy can then synchronize supported external targets through their own adapters. + +### Starting `/zoomies display` + +`slash parser → Keep Awake controller → backend capability check → fixed OS launch → ownership record → accessory/status presentation` + +On macOS this creates a display assertion outside the shell. A backend without display support reports that limitation instead of pretending it worked. + +### Detaching and resuming a session + +`/detach → journal checkpoint → frontend disconnect → service retains shell + output events → nmsh --attach → same shell attachment → journal restore + event replay → composer or active-program passthrough` + +`/resume` also offers saved transcripts. Selecting an archive restores its presentation into the current window without restoring its old shell, as described in section 8. + +### Applying a Theme Bridge target + +`enable Bridge + choose Neovim Follow NMSh → resolve semantic palette → validate generated colorscheme → check artifact ownership → write artifact + ledger → review activation diff → apply verified hook` + +Later theme changes can regenerate the unchanged owned artifact. User modifications cause a conflict. The editor must load/reload the colorscheme for its display to change. + +## 24. Deeper reading + +These documents expand particular areas. Older design and research records explain decisions at their time; current code remains authoritative. + +- [Shell adapters](docs/architecture/shell-adapter.md): backend contracts, differences, switching, and ordinary-shell handoff. +- [Terminal stack](docs/architecture/terminal-stack.md): layer terminology; its zsh-only and bottom-editor wording predates the current adapters/layouts. +- [Terminal hosts](docs/architecture/terminal-host.md) and [multiplexer interoperability](docs/architecture/multiplexer-interop.md): capability boundaries and nested-terminal behavior. +- [notMyUI](docs/architecture/notmyui.md) and [TUI primitives](docs/architecture/tui-primitives-v012.md): common controls, surfaces, and focus. +- [Context Modules](docs/architecture/context-modules.md): capabilities, facts, modules, surfaces, Context Packs and agent context; [Context Engine design](docs/design/context-engine.md): scheduler, invariants and test gates. +- [Prompt customization](docs/architecture/prompt-customization.md): Native context, shape, and appearance choices. +- [Supported tool configuration](docs/architecture/supported-tool-configuration.md): configuration classes, adapters, and ownership. +- [Theme Bridge design](docs/design/theme-bridge.md): imports, target behavior, environment sink, and generated-file safety. +- [Chroma and UI chrome](docs/design/chroma-and-ui-chrome.md): how decoration and semantic presentation fit together. +- [Structured execution](docs/design/structured-execution.md) and [session interaction](docs/design/session-interaction-ux.md): command blocks and viewport interaction decisions. +- [Session journal](docs/design/session-journal-v0.4.md) and [sessions, agents, and intelligence](docs/design/v016-sessions-agents-intelligence.md): persistence and later session capabilities. +- [Shell UX and Ask](docs/design/v016-shell-ux-ask.md): typed assistance and shell interaction policies. +- [Accessibility baseline](docs/accessibility/baseline.md): color, motion, glyph, keyboard, and host considerations. +- [SECURITY.md](SECURITY.md): trust model and reporting policy. +- [CONTRIBUTING.md](CONTRIBUTING.md): development setup, validation, and contribution workflow. diff --git a/website/src/content/reference/background-tasks.md b/website/src/content/reference/background-tasks.md new file mode 100644 index 00000000..90b310ca --- /dev/null +++ b/website/src/content/reference/background-tasks.md @@ -0,0 +1,209 @@ +--- +title: "Background task architecture" +description: "Canonical background task architecture from the v0.20.0 published source." +group: Technical reference +order: 117 +source: "docs/design/background-tasks.md" +--- + +Issue: [#382](https://github.com/raiseCatError/notMyShell/issues/382). A core terminal workflow feature: run +long-running commands (`adb logcat`, `tail -f`, `journalctl -f`, `docker logs -f`, dev servers, watchers, `ping`, +`scrcpy`) beside the normal shell instead of in front of it. + +Status: **integrated on `release/v0.20.0`**: the session owns its tasks (`SessionTasks` in the session service and in +the in-process client), the protocol carries them as the `background-tasks` feature, and the frontend wires the task +bar, the suggestion, `/background` and the output view. Physical QA is pending (below). + +## Why a separate, supervised process + +The real shell runs one foreground job at a time; NMSh's composer, queue (#357) and input awareness (#348) all depend +on that. Its job control (`&`, `bg`, `fg`) belongs to the person and stays untouched. A background task therefore +cannot be "the shell, but in the background". It is an **NMSh-owned process in its own pseudo-terminal**: + +- a PTY, because programs behave as in a terminal (line buffering, colors, `scrcpy`/`adb` checking `isatty`), exactly + like the foreground would show them; +- its own session and process group (the PTY makes the child a session leader), so a stop reaches the task's + processes, including pipeline stages, and nothing else. + +Alternatives rejected: sending `&` to the live shell (the shell's job then shares the foreground terminal, its output +interleaves with every command, and NMSh cannot stop it truthfully); pipes instead of a PTY (changes program +behavior); moving a running foreground command (impossible without changing its controlling terminal and session; +NMSh never does it, never kills and re-runs it). + +### What a task inherits, and what it does not + +The command text is passed **as one argument** to the session's own shell: ` -c `. NMSh never +re-quotes, concatenates or re-parses it, so quoting means exactly what it means in the shell (tested with mixed +quotes, `$` in single quotes and escaped double quotes). + +A task runs in the live shell's reported working directory, with the session's launch environment plus the live +`PATH`. It does **not** see the live shell's aliases, functions, unexported variables, options, or variables exported +after the session started. The suggestion says so, and the detector never recognizes commands that obviously depend +on live state (expansions, substitutions). If a command needs that state, the person runs it in the foreground. + +## Detection (`detector.ts`) + +Conservative and explainable; nothing is executed or expanded, and classification of 2,000 realistic commands takes +well under 500 ms in tests (it runs only on submission, never per keystroke). + +- A command is recognized only when its words say it keeps running: `adb logcat` (but not `adb logcat -d/-c/-t N`, + `adb devices`), `tail -f/-F/--follow` (not plain `tail`), `journalctl -f`, `docker|podman|kubectl logs -f`, + `kubectl get --watch`, `ping` without a count, `scrcpy` (not `--version`/`--list-*`), `tsc --watch`, + `webpack serve|--watch`, `cargo watch`, `python -m http.server`, `php -S`, `rails server`, `flask run`, + `hugo server`, `jekyll serve`, `ng serve`, `vite`, `next|nuxt|astro dev|start`, `nodemon`. +- Package scripts `dev`, `start`, `serve`, `watch`, `preview`, `storybook` are only **likely** (it depends on the + project); `npm test` and builds are not recognized. +- adb's global `-d` (USB device) is told apart from logcat's `-d` (dump and exit). +- Only plain pipelines are classified (the first stage decides: `adb logcat | grep X`). Substitutions, expansions in + double quotes, redirections, heredocs, lists, background jobs, subshells, multi-line input, `sudo`/`doas`/`ssh`, + and full-screen or interactive programs (`top`, `less`, editors, `watch`, `tmux`, `fzf`, …) are never recognized. +- Families are specific (`adb logcat`, `npm run dev`), so a remembered decision never covers a whole program. + +## Preferences (`preferences.ts`) + +`background-tasks.json` in NMSh's config directory, written atomically with mode 0600. It stores families and +settings, never commands, arguments or output. + +- Per family: **Always** (run in the background without asking), **Never** (do not ask), or unset (**Ask**). +- Suggestions on or off; remembered Always rules still apply when suggestions are off. +- No global "always background" mode. An invocation can always be forced to the foreground. +- Retention per task (lines and bytes, clamped to 100–100,000 lines and 64 KiB–32 MiB), finished tasks kept, + notifications (never / on failure / always), bar collapsed or expanded, whether Up enters the bar. + +`decide()` turns a classification and the preferences into foreground / suggest / background. + +## Supervisor (`supervisor.ts`) + +- `start` validates the command (non-empty, no NUL, bounded), an absolute shell and an existing directory, and a + running-task limit (16 by default), then spawns the PTY. The environment is used for the spawn only; task records + never carry it. +- States come from evidence: `running`; `stopping` after a stop request; `stopped` (it exited after a request), + `exited` (status 0) or `failed` (non-zero or an unrequested signal) **only after the exit is observed**. +- **Stop** is SIGINT to the task's process group, what Ctrl+C does in a terminal. It is a signal, not the byte 0x03 + written to the task's input, which a program in raw mode reads as data. `nextEscalation` offers SIGTERM, then + SIGKILL; each is an explicit request. NMSh's interactive shell, other tasks, and processes that left the group + (an adb server started by `adb logcat` detaches with `setsid`) are never signalled (tested). +- After a stop request the task is the whole process group: a shell that does not `exec` its command (dash, Ubuntu's + `/bin/sh`) dies of SIGINT while a program that ignores SIGINT keeps running, so the task stays `stopping`, and + escalation still reaches the group, until the group is gone (found by Linux CI; reproduced on macOS in tests). +- When the leader exits on its own while members of its group still run (they ignored the hangup), the task says so + (`leftovers`) and they are killed only on an explicit request. The group is probed every 250 ms until it is empty, + however long that takes, and then forgotten, so `leftovers` never goes stale and a later kill request finds nothing + to signal. +- **Remove** deletes a finished task and its output; a running task is never removed, and neither is a finished task + whose processes still run (nothing could stop them afterwards). **Dismissing** a view is a presentation matter and + touches no task. These are three different operations. +- Each stop request records when it was made (`stopRequestedAt`), so a view can pace escalation: a repeated or held + key does not turn an interrupt into SIGKILL before the program had a moment to exit. +- `shutdown` ends every task (interrupt, terminate, kill, with a grace period) for an explicit session exit. +- Output notifications are coalesced to at most one per 50 ms per task; state changes are immediate. +- Group ids and reuse: a process group's id cannot be reused while any member exists. Signals go to the group while + its leader is unreaped, or after the leader exited while the supervisor's probe (signal 0, at most 250 ms old) says + the group still has members; a group seen empty is forgotten. The residual window is a group that empties and whose + id is reused within one probe interval, just as a kill is requested; NMSh cannot close it without a process + handle for groups (none is portable). + +## Output (`output.ts`) + +PTY output is one stream (stdout and stderr merged exactly as a terminal shows them; separating them would need +pipes and change program behavior). Each completed line is sanitized with NMSh's `stripTerminalControls`: colors, +OSC titles and hyperlinks, cursor movement, C0/C1 controls and bidi overrides are removed, so a log line cannot +restyle NMSh, move its cursor or pose as another task. `\r\r\n` (a program's `\r\n` through a PTY) ends a line; a +lone `\r` overwrites it, so progress lines keep their last state. Lines are numbered (`seq`), so a view knows what is +new; the stream is bounded by lines and bytes, counts what it dropped and what it received, and splits endless lines. +An 8 MiB hostile burst stays within a 64 KiB bound in tests, and line handling is linear: a 64 Ki run of carriage +returns, which a trailing-`\r` regular expression took seconds over, is a test. + +## Lifecycle and ownership (integration) + +The supervisor belongs to the **session service** (`nmshd`), one per session, beside the session's shell PTY: + +| Event | Behavior | +| --- | --- | +| Frontend closes or detaches | tasks keep running (they are the service's, like the shell) | +| Reattach | the frontend receives the task list and each task's bounded recent output; nothing is started again | +| Session exited or deleted explicitly | `shutdown`: interrupt, then terminate, then kill what remains; the result is reported | +| Service restart or crash | tasks of the previous service are **lost**: listed as such, never claimed as controlled, never restarted | +| Session restore from the journal | finished task records may be shown; nothing is re-run | + +Journal: task records (id, command, family, cwd, times, state, exit status) may be journaled; output follows the +existing journal budget and privacy rules, and the environment is never stored. + +### Service protocol (to add with the integration) + +Client → service: `task-start {command, family?, cwd}` (the service supplies shell, environment and PATH), +`task-stop {taskId, level}`, `task-remove {taskId}`, `task-output {taskId, after: seq}`. +Service → client: `tasks {tasks}` on attach and on change, `task-output {taskId, lines, partial, dropped}`. +A service without the feature does not list `background-tasks` in `SERVICE_FEATURES`; the frontend then offers no +background execution and says why. + +### Frontend contract (TerminalApp, after #359's checkpoint) + +1. **Submission.** Before sending a submitted command to the shell, call `classifyCommand` and `decide`. `foreground` + → unchanged path. `suggest` → show the suggestion strip (UI PR) and wait for the choice; Enter keeps the normal + foreground path. `background` → `task-start`. +2. **Never while foreground input is owned.** No classification or task-bar key handling while a foreground command + is running or waiting for input (#348), while passthrough owns the terminal, or for queued commands (#357's queue + keeps its foreground semantics). +3. **Transcript.** Each task gets an NMSh-owned block (`Background: adb logcat · t3`) with its live output, folded and + bounded; foreground command blocks never receive task output, and task output never enters the shell's PTY. +4. **Keys and the bar** are in the UI PR (`background-task-ui.md`). + +Conflict-prone paths: `src/app/TerminalApp.ts`, `src/session/SessionService.ts`, `src/session/SessionProtocol.ts`, +`src/commands/slashCommands.ts`. + +## As integrated (v0.20) + +- **Owner.** `SessionTasks` (one per session) wraps the supervisor. Tasks keep running while a window is detached; a + reattached window receives the list and each task's recent output (at most 1 MiB per update, newest first) and + nothing is started again. Ending the session ends its tasks; a shell switch keeps them. A service restart ends its + tasks with it (they are its child processes) and none is adopted. +- **Protocol.** Client `task {change}` (start, stop, remove as JSON); service `tasks {state}` and + `task-output {taskId, update}`, sent only to a frontend that listed `background-tasks` and only after + `created`/`attached` and the replay; a refusal is `error` with code `task`. Every request, record and update is + validated on arrival. Task output never travels in the shell's stream or reaches its PTY. +- **Environment.** A start carries the live shell's PATH; the owner supplies the session's shell and its launch + environment, used for the spawn only. Output retention on the owner is the default (5,000 lines, 2 MiB); a window's + mirror applies the person's retention preference. +- **Submission.** Only `routeComposer`'s direct submission (an idle composer, no running command, startup finished, no + passthrough) consults the detector. Queued entries, program input, history reruns, install retries and presets never + do. **Suggestions are off by default**: a recognized command runs in the foreground exactly as before unless the + person ran `/background suggestions on` or set its family to Always. `/background run` is the explicit path. +- **Transcript.** A task gets one NMSh line when it starts and one when it ends (failures by default, per the notify + preference), never its output; the output lives in the bar, the view and `/background`. Copies, reports, + comparisons and failure navigation therefore never see task output. +- **Keys.** Up from an idle composer: an opt-in keymap binding, then a shown agent shelf, then the task bar, then + history. Shift+Up enters the bar from an empty composer. The bar, the manager and the output view are protected + owners for the workspace router. + +## Security review + +| Concern | Handling | +| --- | --- | +| Command meaning | one argv element to the session's shell; no NMSh parsing or quoting | +| Automatic execution | only for a family the person set to Always; never from the heuristic alone | +| Environment and secrets | used for the spawn only; not on records, not in preferences; journals keep no environment | +| Signals | only to the task's own process group, only while its leader is unreaped; escalation explicit | +| Foreign processes | never signalled; detached helpers survive (tested) | +| Hostile output | sanitized per line; bounded; cannot reach the foreground PTY | +| Resource exhaustion | per-task line/byte bounds, running-task limit, coalesced notifications | +| Multiple NMSh instances | tasks belong to one session in one service; another frontend sees them only by attaching to that session | +| Restart/crash | unknown tasks are lost, not adopted | + +## Tests + +`tests/backgroundSupervisor.test.ts` (real PTYs and controlled programs): a logcat-like stream stopped by SIGINT and +reported only after exit; a program ignoring SIGINT stays `stopping` until an explicit SIGTERM, one ignoring both +until SIGKILL; a pipeline stopped as a whole while a detached adb-server-like helper and another task keep running; +leftover group members reported and killed only on request, a leftover group that ends later noticed and forgotten +and never removed while it runs; exact quoting; exit status; environment never on records; +refusals; an 8 MiB hostile burst. `tests/backgroundDetector.test.ts`: recognition and refusal tables, timing, +decisions, preference normalization and private storage, output line handling. + +## Physical QA (not performed) + +- Real `adb logcat` with a device or emulator: stream, stop with `x`, confirm the adb server and other adb commands + keep working. +- Real `scrcpy`: the window closes on stop; escalation if it does not. +- A dev server (`npm run dev`) on macOS and Linux; WSL2 where available. +- Detach the frontend while tasks run, reattach, confirm state and output. diff --git a/website/src/content/reference/changelog.md b/website/src/content/reference/changelog.md new file mode 100644 index 00000000..6204a8ec --- /dev/null +++ b/website/src/content/reference/changelog.md @@ -0,0 +1,628 @@ +--- +title: "Canonical changelog" +description: "Canonical canonical changelog from the v0.20.0 published source." +group: Technical reference +order: 115 +source: "CHANGELOG.md" +--- + +All notable changes to NMSh are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Notable user-visible changes are added under [Unreleased] as they land; see [CONTRIBUTING.md](CONTRIBUTING.md#changelog). + +## [Unreleased] + +## [0.20.0] - 2026-10-18 + +**Terminal workflow, first-run setup, background tasks & native modules.** The first stable release since v0.18.0. A new first run lets you pick a starting point under a live aurora and review it before anything applies; queued commands read as a chronological timeline with saved summaries; completed output can be selected, copied and acted on from the keyboard; optional tools are discovered, reviewed and installed through one flow; long-running commands can run as background tasks beside the shell; and the native module catalog covers twelve more runtimes and build tools. Every new control is reached through an explicit command, key or review, and the persistent shell, the queue and program input keep their behavior. + +**v0.19.0 was never published.** Its terminal workflow (input awareness, command queue, copy and Smart Folding, Copy as Report, Compare output, references, paste review, pins and recipes, activity wording) ships for the first time in this release; there is no v0.19.0 tag or download. Its notes are kept below as [0.19.0 — not released separately](#0190---not-released-included-in-0200). Upgrading from v0.18.0 brings both sections. + +Verification: one full canonical `verify:release` on the release commit (build, the complete unit and real-PTY suite, benchmark typecheck and timing smoke) and exact-commit CI (Ubuntu and macOS shards, Node 22 and 26, Fedora portability subset, both timing smokes). Physical-terminal evidence: the maintainer tested and accepted the first-run starting point, guided Setup and Tools flow on macOS; the formal v0.19 and v0.20 checklists were not completed item by item, and no physical Linux, Omarchy or WSL pass has been made ([v0.20 physical QA record](docs/testing/v020-physical-qa.md)). + +### Added + +#### First run: starting points under a reactive aurora +- A fresh installation opens one starting point: four choices (**NMSh Recommended**, **Minimal Terminal**, **Customize Everything**, **Keep Existing**) under NMSh's real Aurora engine, with Vespyr on the horizon. Highlighting a choice changes the sky's mood in a short cross-fade; nothing applies until you review the choice and press Enter, and Esc asks *Leave setup?* with Stay first. `A` cycles the backdrop through Aurora, Subtle and Off and is saved at once. Reduced Motion, Effects Off, `NO_COLOR` and 16-color terminals get a still or plain sky, and every choice, Apply and Leave stay reachable down to 16 × 6. +- Apply folds the sky into one rule and raises the real composer into place; a genuine first run adds Vespyr's welcome once, whichever starting point it chose. Later applies add one receipt line. +- **Keep Existing** reads your environment read-only (Starship, Powerlevel10k or Oh My Posh, colors and tools) and keeps what it finds; it can also offer NMSh Native or another installed prompt. No file outside NMSh's configuration is changed. +- **Minimal Terminal** starts Two-line (←→ One-line) with a quiet Flow composer and no greeting; an existing greeting is hidden, not deleted. +- **Customize Everything** opens the guided setup in the selector's visual language (route of steps, *Step N of M*, keycaps, a live preview from 96 columns). Esc returns to the choices without closing setup. The guided setup keeps one fixed frame, and editors it opens return to the same step, row and unapplied draft. +- `/startup`, `/setup quick` and Settings → Starting point reopen the selector; `/setup` opens the guided setup directly, and `/setup prompt|look|motion|cursor` stay direct. +- On the first run only, where NMSh can prove the window is its own (Terminal.app; kitty on macOS with a remote-control socket; Hyprland 0.55+ with a Lua configuration), it offers to maximize the window for setup and restores it afterwards if it is still as NMSh left it. Nothing about the offer is saved ([first-run window maximization](docs/testing/first-run-window-maximization.md)). + +#### Optional tools: one discovery and install flow +- `/tools`, Settings → Tools, the guided setup and the starting point's review share one panel and installer. Tools have tiers: **Recommended** (ripgrep, fd, fzf, jq, bat, zoxide, tealdeer) and **Enhanced** (for example eza, gh, lazygit, delta, Atuin, jc, Glow, xh, direnv, ShellCheck, shfmt), plus the rest of the curated catalog. `V` switches the view (Recommended → Recommended + Enhanced → All curated tools; Recommended + Enhanced by default) without changing the selection. +- Space selects one tool, `R` and `E` add the missing Recommended or Recommended + Enhanced tools, `C` clears; keys never type into search (`/` focuses it). Enter reviews the exact package-manager command of every selected tool, then one confirmation with semantic buttons (No first; green *Yes, install*; red *Yes, uninstall*) and per-tool results. +- A check that fails or does not finish reads *Unable to verify*, never Missing, and is never selected or installed; a missing tool without an installer on this system reads *Unsupported here*. Installing zoxide, fzf or Atuin never turns on their shell hooks. Fastfetch stays a supported welcome provider without a tier. + +#### Execution timeline and queue +- Queued commands are shown as a safe chronological timeline: the running command, up to two recent results and up to two upcoming commands, with semantic motion that respects Reduced Motion. +- `/settings queue` and `/queue settings` open one Queue destination: queue behavior, failure policy, the live timeline, recent and upcoming counts, completed-view retention, queued output display and saved summaries. Short forms: `/q`, `/qp` or `/pause` (pause), `/qr` (review, then resume), `/qc` (review, then clear). +- **Queued output folding:** with *Fold queued output*, a successful queued command's long output folds behind a hint and `Ctrl+O` expands or collapses it. Folding is presentation only: copies keep every line, tabs and Unicode. Inline is the default; failures, interruptions and output you are inspecting stay expanded. +- **Queue summaries:** settled chains are kept for review (`/queue history`, newest first, with results on demand) and can be saved as typed, privacy-safe summaries in the session's transcript (`/queue save-summary` after an exact-text review). Saved summaries hold observed facts only; restoring or reattaching never runs anything. +- **Multi-line paste choices:** reviewing a multi-line paste offers Run as block, Queue separately, Edit or Cancel. Separation that would not be safe is refused with the reason; block and edit stay available. + +#### Output selection and Actions +- `/copy` selects completed output from the keyboard: Up/Down moves between outputs, Enter copies, Esc returns to the draft and view. `/actions` (and `/actions N`) opens the actions for a completed output. Saved Quick Copy and picker preferences still apply, and the selected surface stays continuous across prompt segments, wedges and inverse output. + +#### Colors, cursor and updates +- Choosing Custom colors opens a real editor at once: ten presets (Lavender, Purple, Blue, Cyan, Teal, Green, Amber, Orange, Rose, White), a live preview, each stop's hex, `S` to apply and Esc to cancel. The cursor idle effect gets its own colors (Follow cursor effect by default, as before). +- `/cursor` offers Purple, Blue, Green, Classic, Follow theme and Custom Fire colors. Auto uses the portable effect until a supported native integration is configured; the terminal still controls its physical caret color. +- With update notifications set to Notify, a newer stable release shows one short amber notice in the transcript, once per version, with the way to update *this* installation: brew commands for Homebrew, `/update` for a verified source checkout, the release page otherwise. + +#### Workspace sidebar and detail panel — experimental, Off by default ([#330](https://github.com/raiseCatError/notMyShell/issues/330)–[#335](https://github.com/raiseCatError/notMyShell/issues/335)) +- **Experimental and Off by default.** Turn it on in Settings → Layout → Workspace sidebar & panel (or Setup → Editor). While Off, `/sidebar` and `/panel` say how to turn it on and open nothing, and `keymap.json` workspace bindings are kept but take no key (`/keys` says so). Turning it Off closes open regions and gives the shell every column again. `/worktrees`, `/sessions`, `/github` and `/background` never depend on it. The docked layout is expected to be replaced by integrations with dedicated Git tools in a later release. +- When on, `/sidebar` lists tmux panes of the server NMSh runs inside, this repository's worktrees and links to `/github`, `/agents` and `/sessions`; `/panel` explains the selected item. `/sidebar focus` and `/panel focus` give them the keyboard; Esc returns, Tab moves between regions, a click focuses a region. +- On a wide terminal the regions dock beside the transcript and the shell is sized to the middle; on a narrow one a region opens as a drawer over the transcript only while it has the keyboard, and the shell keeps its width. Fullscreen programs and idle visuals hide them. +- Opening a worktree stages its `cd` in an empty composer; tmux navigation selects panes and windows by id through tmux from fixed system locations. Nothing runs by itself. +- `keymap.json` binds workspace keys, opt-in and validated (composer keys must say what they replace); `/keys` shows the keys in effect, problems in the file and keys the terminal or tmux may take first; `/keys reload` reads it again. + +#### Background tasks ([#382](https://github.com/raiseCatError/notMyShell/issues/382)) +- `/background run ` (`/bg run`) starts a long-running command (`adb logcat`, `tail -f`, a dev server) in its own terminal beside the shell, in the shell's directory with its PATH, without the shell's aliases, functions or unexported variables. +- A task bar above the composer: Up from an idle composer (after a shown agent shelf) or Shift+Up focuses it; Enter opens a task's output, `x` stops it (again later: terminate, then kill, paced so a held key cannot jump to SIGKILL), Delete removes a finished one. `/background` lists every task with details and preferences; `/background t3`, `stop`, `kill`, `remove` and `clear` act directly. +- Tasks belong to the session: they keep running while the window is detached, a reattached window shows them and their recent output, and ending the session ends them. Task output never enters the shell, the transcript, copies, reports or comparisons; the transcript gets one line when a task starts and when it fails. +- Suggestions are **off by default**. `/background suggestions on` offers a recognized command's family when you submit it: `b` background once, Enter here as before, `a` always, `n` never ask, Esc back to the draft. + +#### Native modules ([#364](https://github.com/raiseCatError/notMyShell/issues/364)) +- Runtimes: .NET, Swift, Zig and Deno ([#370](https://github.com/raiseCatError/notMyShell/pull/370), which also becomes the canonical Ruby and PHP implementation), Elixir, Dart and Flutter ([#372](https://github.com/raiseCatError/notMyShell/pull/372)). Requested versions come from the project's own files, active versions from the install location; no tool is ever run and a binary inside the workspace is refused. +- One `build` module for Bazel, Gradle, Maven, CMake, Meson and Make with the version the project pins ([#373](https://github.com/raiseCatError/notMyShell/pull/373)). +- Mercurial bookmark or branch and Jujutsu repository state in the version-control extras pack ([#374](https://github.com/raiseCatError/notMyShell/pull/374)). + +#### Managed Claude Code ([#347](https://github.com/raiseCatError/notMyShell/pull/347)) +- `/model` and `/effort` as native pickers over Claude's documented control requests; a change shows as requested until Claude reports it. +- Claude mods with their real types and states per launch identity (read only). Turning a Claude plugin on or off from NMSh is **off in this release**: it was never verified against a live account and it would write your Claude settings, so the row names the `claude plugin enable|disable … --scope …` command to run yourself. Behind the experimental `NMSH_EXPERIMENTAL_CLAUDE_PLUGIN_TOGGLE=1`, it goes through Claude's own command at the installed scope and running managed sessions reload. NMSh never edits Claude's settings files. Managed Claude stays beta; its physical QA was deferred for this release. +- A context panel beside the agent conversation, provenance on every runtime fact, and Markdown rendering for agent output that reveals real link destinations and refuses smuggled characters. + +#### Worktrees and controls +- `/worktrees` shows a related pull request only from evidence: the last `/github` listing of the same repository, with the PR's head branch in its own repository exactly the worktree's branch; `p` opens it in `/github` ([#381](https://github.com/raiseCatError/notMyShell/pull/381)). +- A shared action-control renderer whose states are told apart by marks, not color, first used by the `/worktrees` confirmation ([#369](https://github.com/raiseCatError/notMyShell/pull/369)). + +### Changed +- **Fresh defaults are NMSh Recommended:** Lavender Native, Rich motion (Expressive context and events, Sweep launch, Seal completion, cursor travel), Chat transcript with grayscale history, Smart folding, automatic typed queue summaries and a Project + Session window title. Cursor effects start Off with a theme-colored caret (choosing Fire starts on the Classic flame), and Minimal starts Two-line. **Saved configurations keep their behavior:** fields missing from an existing file read as their previous values (no added motion, no cursor effect, Normal transcript, summaries and title Off), and explicit and unknown fields survive saving. +- Setup runs Look → Prompt → Motion, and Chroma is chosen in Look only. Setup previews show the real result (real screen-saver previews, a Chroma preview that follows the setting and says what hides it, a legible activity-wording example). +- Menus share one set of selection glyphs with ASCII forms under Safe glyphs; the selection band marks the cursor only, and status words such as *✓ Selected* carry the selection. Panels opened from Settings return to Settings. +- Confirmations use semantic Yes/No controls: No is focused first, and an install or uninstall names what it will do. +- The background task bar is readable at rest; finished tasks show their outcome briefly (6 s, 30 s after a failure) and then stay listed in `/background`. +- NMSh's own lines (`/copy` confirmations, notices, completion rows) wrap at word boundaries in narrow windows, with continuation rows indented under the text instead of splitting words such as `5 l`/`ines`. Copying a selection still gives the line exactly as stored. Shell output keeps the terminal's column wrapping ([#349](https://github.com/raiseCatError/notMyShell/pull/349)). + +### Fixed +- **Input ownership:** replies to `read`, `read -k 1`, `read -n 1` and hidden reads are no longer queued, left in the composer or lost, however soon they are typed; Ctrl+C or Ctrl+Z ends a waiting question and what you type next is for the prompt. A waiting read is shown in its own color with the program's question and one instruction. +- **Exact `/copy`:** copies are the bytes the program printed, tabs and the final newline included. +- **Composer history while a command runs:** ↑/↓ recall history again while a command that reads nothing is running; a program reading input, a full-screen program or an edited queue entry keeps the keys. Recalled text reaches the queue only through Enter, and `/shell` starts navigation fresh in the new shell. +- Queued, attached and restored commands get the composer's syntax colors once their command word is classified. +- The queue shows what runs next and in what order, and resume and approve are reviewed in place; nothing runs from one key and a typed `y` never approves. Paste review never lets an NMSh command label mask what the shell would run. +- Pausing the queue or declining an approval is acknowledged only once the session owner reports it, and the draft is kept. An interruption (Ctrl+C, exit 130) holds pending queued work. +- The selected surface no longer lets prompt segment backgrounds, Powerline connectors or inverse video punch through it. +- Vespyr is composited pixel by pixel, with no background rectangle behind the cat. +- Tools: recommended tools stay listed once installed, and a tool check's timeout always fires. +- Background tasks signal only their own process group through a task-local guard, never a saved numeric group id, so a reused process id cannot be signalled. +- The Codex app-server adapter sends a cancel made before Codex names the turn as soon as it does (the adapter remains unwired). + +### Security +- Approval prompts show everything they approve: a multi-line command is shown on every line (cut at 600 characters with the remainder counted), so a harmless first line can no longer hide what follows; this fixes the shipped Claude adapter's permission prompts ([#380](https://github.com/raiseCatError/notMyShell/pull/380)). +- Executable Portable Mods stay disabled behind a documented threat model and a regression gate; the only processes the mods code may start are Claude's own `plugin` commands in four reviewed forms, without a shell ([#379](https://github.com/raiseCatError/notMyShell/pull/379)). +- Background tasks: every request, task record and output update crossing the session boundary is validated; task output is sanitized line by line and bounded; signals reach only a task's own process group; the environment is used for the spawn only and never stored. +- Workspace text from tmux, Git and keymap files has whole escape sequences removed before it is drawn. +- Saving a queue summary goes through the same scrollable exact-text review as reports; compact display can never hide persisted command arguments, redacted text cannot be edited in that review, and automatic summaries keep recognized command labels only when the session owner observed that the command is eligible for history. +- Reviewed paste blocks submit exactly the entries you kept, in the order shown; a removed or empty block can never submit the original paste. +- Keep Existing detection and the window-maximization probe are read-only; the probe never changes a window it cannot prove is NMSh's own. + +### Compatibility +- Node.js 22 or newer; zsh, Bash 4.4+ and Fish, as in v0.18.0. CI covers Node 22 and 26 on Ubuntu, Node 26 on macOS, and a Linux portability subset on Fedora. +- Queue history, summaries and acknowledged pause need a session service from this release; a service started by an older NMSh keeps the older queue behavior until it restarts. +- Background tasks need a session service from this release (a service started by an older NMSh says so until it restarts) and POSIX process groups (macOS, Linux, WSL 2). +- The tmux section of the sidebar needs tmux in `/opt/homebrew/bin`, `/usr/local/bin`, `/usr/bin` or `/bin`. + +### Upgrade notes +- **From v0.18.0:** settings, themes, modules, sessions and history carry over. An existing installation does not see the first-run starting point (`/startup` opens it any time) and keeps its saved presentation; fields it never saved read as their previous behavior. The v0.19.0 upgrade notes below also apply: activity wording becomes Expressive and the command queue is on by default. +- With update notifications on Notify, NMSh shows one amber notice per new stable release; Off checks nothing. +- Nothing new is on after the upgrade: the experimental sidebar and panel are Off (a configuration without the new `workspacePanels` setting reads as Off, and nothing else in it changes), the composer binds no workspace keys, and background suggestions are off. +- Restart the session service (or start a new session) to use background tasks, queue history and summaries. +- Homebrew: `brew upgrade raiseCatError/tap/nmsh` installs whatever version the tap's formula points to; check `nmsh --version` (see the README for the tap's status). + +### Known limitations +- **Physical-terminal evidence is partial.** The maintainer tested and accepted the first-run starting point, guided Setup and Tools flow on macOS; physical QA during stabilization found the input-ownership, exact-copy, queue and Setup issues fixed above. The formal checklist passes for background tasks, the new modules, managed Claude and the v0.19 workflow were not recorded item by item. No physical Linux (including Omarchy/Hyprland), WSL 2, iTerm2, kitty, WezTerm or Windows Terminal pass has been made; Linux remains beta on automated CI. The experimental workspace regions, drawers and keymap host notes are not physically tested. +- The first-run window offer on Hyprland/Omarchy and kitty is covered by automated tests only. +- Clicks focus a region but do not select sidebar rows; the mouse wheel scrolls the transcript wherever the pointer is. +- The sidebar lists tmux and worktrees only; NMSh sessions and managed targets as rows, and module diagnostics in the detail panel (they stay in `/modules` details), are not included. +- Background tasks run in a separate shell: aliases, functions and unexported variables of the live shell are not available. A service restart ends the tasks it owned. Output kept by the task owner is 5,000 lines or 2 MiB per task. +- The Codex app-server adapter is included but not wired: Codex and OpenCode sessions are shown, not controlled. Claude Remote Control stays unsupported for managed targets. +- Claude mod enable/disable has not been exercised against a live Claude account in this release's verification. +- Everything listed under v0.19.0's known limitations still applies. + +## [0.19.0] - Not released (included in 0.20.0) + +**Terminal workflow.** One command's result becomes useful for the next without NMSh taking execution away from the real shell. NMSh notices when a running command is waiting for you, lets you prepare the next commands while one runs, copies exactly what you choose, turns output into reviewed reports and comparisons, and keeps the commands you reuse. Nothing derived from output, the clipboard, a pin or a recipe runs without an explicit action, and every queued command still runs in your one persistent shell. + +Verification: automated CI (unit and real-PTY suites on macOS and Ubuntu, Node 22 and 26, a Fedora portability subset and the timing smoke) on the integrated head, plus the per-feature canonical, Linux/Fish 3.7 and report/compare/clipboard security gates recorded in [the frontend freeze handoff](https://github.com/raiseCatError/notMyShell/blob/ca26b9be62f2d7eb7f083e05bd6a2fcff9334811/docs/development/workflow-frontend-freeze.md). This candidate was never published on its own: no v0.19.0 tag or release exists, and everything below first ships in v0.20.0. Physical-terminal status is recorded with v0.20.0 ([v0.19 physical QA checklist](docs/testing/v019-physical-qa.md)). + +### Added + +#### Input awareness ([#348](https://github.com/raiseCatError/notMyShell/pull/348)) +- **Input awareness:** a command that waits for an answer is no longer just "Running". NMSh reads the terminal's own state, without touching the program. A password or other no-echo read, or a single-key question such as `e2fsck`'s `Fix?`, is confirmed; on Linux, any blocking read of the terminal is confirmed too. An ordinary line question left open by an idle process is shown as "Probably waiting for input". The question stays visible beside the composer however far the transcript has scrolled, and the window title, `/sessions`, `/resume` and a cross-session notice show the wait. Hidden and single-key input goes straight to the program, keystroke by keystroke, and is never shown or kept. A reply left unsent when the command ends is discarded, never run as a shell command. Waiting time is measured separately from wall-clock time and reported on completion. See `docs/design/input-awareness.md`. +- Fish's own `read` and other builtin readers are recognized from authenticated terminal ownership, and input typed during startup, before the shell is ready, or while a hidden read is open goes only to the program that currently owns the terminal. + +#### Command queue ([#357](https://github.com/raiseCatError/notMyShell/pull/357)) +- Commands typed while another runs are **queued in the same shell** and run after it, one at a time, only when the shell reports it is ready, keeping the directory, variables and functions. Multiline entries keep their meaning (Fish `begin`/`end`, zsh and Bash brace groups). `/queue` shows, edits, reorders, pauses, resumes or clears them; queued command text is drawn, never interpreted. +- When the running program is waiting for an answer, Enter answers it and **Ctrl+Q** composes a queue entry instead. While a program reads keys directly, Ctrl+Q takes the next command for the queue and **Ctrl+S** sends the composer's text to the program. +- A failure or interrupt **pauses** the rest of the queue. Detach and reattach never submit an entry twice; switching shells pauses the queue, and a shell that exits drops its entries instead of replaying them in another shell. Silence is never taken as completion. + +#### Copy and Smart Folding ([#358](https://github.com/raiseCatError/notMyShell/pull/358), [#350](https://github.com/raiseCatError/notMyShell/issues/350)) +- `/copy N`, `/copy -N` (the latest N), ranges `A-B` and lists `1,3,5`; `--status` adds the completion row; `/cp` is the same command in every form. +- `/copy ui` opens an interactive picker; `/copy settings` chooses Quick Copy or the picker by default and whether copies include status. +- A sticky Copy control keeps block actions reachable while a long block's header is scrolled away. +- **Smart Folding** keeps long diagnostics readable; the full output stays stored, and copies include folded lines. + +#### Copy as Report ([#360](https://github.com/raiseCatError/notMyShell/pull/360), [#351](https://github.com/raiseCatError/notMyShell/issues/351)) +- One or several command blocks as a Markdown or plain-text report with command, output, directory and exit status, always shown for review first. Enter copies exactly the reviewed text; cancel leaves the clipboard unchanged. +- Likely secrets and private paths are **redacted** in the review before any Markdown is added; edited text is rescanned, changed lines are marked, and an unredacted choice is labelled. Redaction is best-effort (see Known limitations). + +#### Compare output ([#361](https://github.com/raiseCatError/notMyShell/pull/361), [#352](https://github.com/raiseCatError/notMyShell/issues/352)) +- `/compare` (the latest output with its previous run), `/compare N`, `/compare N M` (numbers as for `/copy`), or Actions → Compare output…: a line diff with counts, hunks and collapsed unchanged regions, side by side from 140 columns and unified below, with labelled whitespace normalization. Output lost while detached is marked incomplete. +- In the comparison, `r` opens the diff as a reviewed, redacted report. `c` copies the sanitized plain diff **directly, without redaction** (it shows a caution when the detector flags a likely secret). + +#### References, paste review, clips, pins and recipes ([#368](https://github.com/raiseCatError/notMyShell/pull/368), [#353](https://github.com/raiseCatError/notMyShell/issues/353)–[#355](https://github.com/raiseCatError/notMyShell/issues/355)) +- Files, links, commits and device names in a block's output can be opened, copied or staged from its Actions; nothing runs by itself. +- **F8 / Shift+F8** walk failed commands (by exit status, not text matching) and keep your draft; the palette offers Previous/Next failed command too. +- **Paste review:** a pasted zsh, Bash or Fish sequence can be split into commands and parse-checked without running anything; confirming queues the reviewed list. `/ps` reads the clipboard only when you run it. +- **Queue conditions:** an entry can wait for its own approval (withdrawn if the entry is edited) or be marked `always` to run even after a plain failure, never after an interrupt. +- `/clips`: text deliberately kept in memory for this window, reviewed before insertion and gone when the window closes. Optional clipboard-change hints on macOS (`/clips suggestions on`, off by default) read only the pasteboard's change counter, never its contents. +- `/pins`: pinned commands with their directory (palette → Pin selected history command) and recipes with quoted placeholders, staged or reviewed and queued, never run automatically. + +#### Activity wording ([#367](https://github.com/raiseCatError/notMyShell/pull/367), [#362](https://github.com/raiseCatError/notMyShell/issues/362)) +- **Expressive** (the new default) gives each running command one stable phrase from a curated 663-pair vocabulary (`✦ Cooking… · 18s`, then `✔ Cooked for 18.0s`), kept across detach and reattach; **Classic** keeps plain Running/Completed wording (Settings → Live activity → Activity wording, and Setup). Failures, interrupts, agents and queued runs keep factual wording, and copies, reports and restored records always store the factual form. + +#### Modules and themes +- Module details explain where a fact came from, its freshness, cache retention and measured collection time, and say when a fact is uncollected or unknown rather than implying it; `/doctor` reports Context Engine health ([#365](https://github.com/raiseCatError/notMyShell/issues/365)). +- Ruby and PHP version modules read the project's own files and the install location, never running an interpreter. The Gemfile `ruby` declaration is parsed in linear time. +- `/theme` imports **Starship** configurations as Native themes from their static colors only; format strings and custom commands are never evaluated, and the preview lists what was not carried over. + +### Changed +- **Setup Cat order: Look → Prompt → Motion.** The first steps after Terminal are now **Look** (formerly Appearance): theme family, variant, accent, theme text, UI chrome, then Chroma (the only place Setup chooses it) with its dependent rows under it; **Prompt**, which shows Chroma as a read-only line that goes to Look; and **Motion**, which now starts with Reduced Motion, Decorative effects, Shimmer and Milestone effects above the motion rows they govern. Cursor follows Motion. Every setting stays reachable and saved values are unchanged; `/setup look` opens Look (`/setup appearance` and `/setup chroma` still work). + +### Fixed +- **Single-key prompts:** answering a single-key question through the composer no longer sends a stray Enter that answered the next question with its default. +- **Passwords:** text typed at a password prompt is no longer drawn in the composer. +- Startup and termination: a quit or termination signal at any point during startup cleans up the frontend; a prompt that arrives after the window has closed can no longer restart context collection or queue work. +- Reattaching to a fullscreen program gives it the terminal and its size at once, before the session journal is written. +- Native completion accepts readiness that arrives in fragments (LF or CRLF) within its existing bounds. +- The command directory recorded with a command is the shell's authenticated directory at submission, even when asynchronous context collection still describes the previous prompt. +- Restored command and status rows follow the attaching window's NO_COLOR setting. +- `/sessions` refreshes live session facts without losing the selection, query, draft or focus. +- A clipboard write is reported as complete only when the whole payload was delivered and the clipboard tool exited successfully. + +### Security +- Every session socket request (listings, notices, dismiss, rename, kill), not only create and attach, verifies the private runtime directory's ownership before connecting. +- A client cancelled before it started discards output withheld for it instead of exposing it. +- Report and comparison text is redacted before Markdown is added; hostile command, status and directory text (line breaks, backticks) stays enclosed, with bounded delimiter sizing for backtick-rich data. Report review stays readable at extreme terminal widths. +- Comparison chooser headings and pasted-text review show control, bidi and zero-width characters visibly instead of sending them to the terminal. +- The pins panel shows control and bidi characters in labels and prompts instead of writing them to the terminal. + +### Compatibility +- Node.js 22 or newer, as before. CI covers Node 22 and 26 on Ubuntu, Node 26 on macOS, and a Linux portability subset on Fedora. +- zsh, Bash 4.4+ and Fish. Fish 3.7 on Linux is covered by the frontend freeze verification; automated Bash coverage is 5.x, so the 4.4 minimum was not exercised directly. +- The command queue and queue conditions need a session service from this release: a service started by an older NMSh reports the queue as unavailable until it restarts. +- Clipboard-change hints need macOS; Linux has no supported metadata-only clipboard counter. + +### Upgrade notes +- Activity wording becomes **Expressive** by default, including for existing settings. Choose Classic in Settings → Live activity → Activity wording to keep the previous wording. +- The command queue is on by default (Settings can turn it off). Existing session services keep running; restart them (or start a new session) to use the queue. +- Settings, themes, modules and history carry over unchanged. Clipboard suggestions stay off until turned on. + +### Known limitations +- Physical-terminal status for these features is recorded under v0.20.0's known limitations. +- Redaction is pattern-based and can miss custom, encoded or unknown secret forms and private paths; review every report. Comparison `c` and plain `/copy` copy without redaction by design. +- A clipboard tool that fails after it started writing may already have changed the clipboard; NMSh cannot roll it back. +- Fish can drop type-ahead entered while its own `read` is still running, as in any terminal ([#356](https://github.com/raiseCatError/notMyShell/issues/356)). +- Project and branch facts can lag a fast directory change while collection is pending; a failed `/sessions` poll keeps the last validated view. +- A concurrent resize and quit key in native `less` can lose the quit key; synchronized automated tests do not prove arbitrary physical resize timing. +- A forced SIGKILL of the frontend can bypass helper cleanup; normal stop, SIGTERM and SIGHUP clean up owned helpers. +- Exact last-frame module width-compaction explanations and dedicated external source previews are not included ([#365](https://github.com/raiseCatError/notMyShell/issues/365)). +- Ruby and PHP were the candidate's only new runtimes; the full native runtime set (.NET, Swift, Zig, Deno, Elixir, Dart, Flutter, build systems) is part of v0.20.0. + + +## [0.18.0] - 2026-10-13 + +**Context Engine, Managed Agents & Workspaces.** Context modules become a platform: facts are collected on demand by bounded, privacy-labelled capabilities, the first-party catalog ships as declarative Context Packs, and the Status Strip becomes a full second surface for modules. Claude Code can run as a managed target inside NMSh, with its permission requests and questions answered natively. `/worktrees` and a read-only `/github` workspace are the first delivered slices of the workspace work. The release also carries the fixes from the v0.17 Ghostty physical-QA pass and a set of passthrough, tmux, transcript and Fish correctness fixes. + +Verification: the release gate is automated CI (unit and real-PTY suites on macOS and Ubuntu, Node 22 and 26, a Fedora portability subset and the timing smoke). **Physical-terminal QA has not been performed**; see Known limitations below and the [v0.18 physical QA checklist](docs/testing/v018-physical-qa.md). + +### Added + +#### Context Engine and Context Packs ([#305](https://github.com/raiseCatError/notMyShell/issues/305), [#316](https://github.com/raiseCatError/notMyShell/issues/316)–[#320](https://github.com/raiseCatError/notMyShell/issues/320), [PR #326](https://github.com/raiseCatError/notMyShell/pull/326)) +- **Capability scheduler:** typed capabilities declare what they read, their environment, cost, timeout, cache lifetime and per-field privacy. Only facts that visible, routed modules demand are resolved, off the typing path, with bounded concurrency, coalescing, timeouts with backoff (and a cold-start allowance), cancellation on directory change, staged scopes that keep the old context visible until the new one settles, stale-while-revalidate caching and command-completion invalidation. Hidden modules cost nothing. +- **Shell-reported environment:** each zsh, Bash and Fish prompt reports an allowlisted set of non-secret environment values (and only the presence of credential variables), so context follows the live shell, not NMSh's launch environment. +- **First-party catalog** as bundled declarative packs: project package; Node, Python, Go, Rust and Java versions from pins and install layouts (never by running them); mise/asdf tool requests and direnv state (never reading `.envrc`); Terraform/OpenTofu, Helm, Pulumi; AWS, Google Cloud and Azure from local non-secret configuration (no API calls, no credentials); OS, user/host/SSH, jobs, session duration, time, memory and battery; Git stash and upstream. Every module has a deterministic preview, Safe-glyph labels and Theme/Neutral roles. +- **Declarative Context Packs** (`nmsh.context-pack/v1`): bounded, strictly parsed data naming core capabilities, with license, provenance, compatibility and recommendations. `nmsh packs` lists, inspects, installs (optionally pinned by `--sha256`), enables, disables and removes local packs atomically; a tampered pack reports an integrity failure. Packs cannot run commands or code and gain no authority by being installed; recommendations explain local evidence and never install or enable anything. +- **Claude Code agent context** through Claude Code's official status line: `nmsh agent-status setup` shows and applies one reviewed settings change, and `nmsh agent-status remove` undoes exactly that change. The bridge keeps an allowlisted, private record per NMSh session, and modules show model, effort, context-window use and 5-hour/7-day limits. No terminal scraping; prompts and transcripts are never read. +- **`/modules`** opens the module manager directly and saves each change as it is made; inside `/prompt` it says it edits the draft, and `A` saves module changes alone. `/` searches Modules, Catalog and Packs over loaded data, footers only offer controls that can act, details wrap at narrow widths (the prompt preview yields first), and under Starship, Powerlevel10k, Oh My Posh or Prompt None the manager marks which surfaces cannot show modules and why, keeping their settings. +- `/prompt` → Modules gains **Catalog** (every module by category, with local recommendations) and **Packs** tabs, and a details view saying what a module reads, how fresh each fact is and whether it may enter command history. + +#### Status Strip 2.0 ([PR #328](https://github.com/raiseCatError/notMyShell/pull/328)) +- The strip is one row at the **Top** or **Bottom** of the NMSh pane (inside tmux, Bottom sits above tmux's own status line) with independent **Left, Center and Right** groups. Width fitting is deterministic in display cells: compact forms first, then lowest priority, the center before the edges; Keep Awake still narrows full → short → glyph, and the row hides below 30 columns. +- Styles: Plain with a dot (Minimal), bar (Divided) or space separator, or Powerline using the prompt's own geometry; Safe glyphs and NO_COLOR stay readable. Every built-in module except the exit status can be routed to the strip, with a strip-only group. +- **`/strip` Status Strip Studio:** on/off, edge, style, separator, which system items show and where, and the group and order of modules on the strip, with a live preview from the real renderer at the panel width and at 50 columns. The Minimal, Developer and System presets (Custom after edits) preview what they would change before Enter applies them, `U` undoes, and no preset touches cloud or agent modules or turns the strip on. + +#### Managed Claude Code and `/mods` ([PR #329](https://github.com/raiseCatError/notMyShell/pull/329), [PR #344](https://github.com/raiseCatError/notMyShell/pull/344), [PR #346](https://github.com/raiseCatError/notMyShell/pull/346)) +- **Managed Claude Code targets:** `/claude` focuses or resumes existing work, or opens a launcher listing targets under their launch profile with `+ New target`; `/claude new` always starts fresh, and `/ai` keeps the supervisor. Named launch profiles keep each Claude account's configuration and identity apart. NMSh drives Claude Code through its structured stdio interface, so tool permission requests (allow/deny) and AskUserQuestion questions are answered in NMSh, never by scraping a terminal. Turn results give the real reason ("authentication failed", "Interrupted"), and the model is shown only as Claude reports it. +- **Target shelf:** live targets sit above the composer. Up from an idle composer focuses the shelf, Left/Right select, Enter opens, Down/Esc return. +- **Agent view:** a full-height conversation workspace. A compact welcome (provider, reported model, identity, folder and branch) rests on the composer and gives way as turns arrive; the target's state rides the composer rule; your turns are marked and the agent's prose is the body; tool calls are quiet rows with failures in words. It follows the transcript's Normal or Chat presentation and the composer's Top or Bottom position. The agent draft uses the shell composer's editing geometry and the terminal's own caret, so `/cursor` shape and color apply; Up and Down move through multiline drafts, scrollback reaches the first turn, and a scrolled-back view stays put while new events arrive. +- **`/mods`** (also `/extensions`, `/claude mods`, `/codex mods`): one searchable, keyboard-first inventory of portable mods, provider-native plugins and settings hooks, and Context Packs, in NMSh's own tabs, grouped lists and selection bands. Rows give one state word with scope and version; the selected entry's execution facts stay visible, and Enter opens its full execution and provenance record. Provider plugins are labelled as running inside the provider with no NMSh sandbox. Discovery reads files only and never runs mod code. + +#### Worktrees and GitHub (first slices of [#337](https://github.com/raiseCatError/notMyShell/issues/337) and [#338](https://github.com/raiseCatError/notMyShell/issues/338)) +- **`/worktrees`** ([PR #342](https://github.com/raiseCatError/notMyShell/pull/342)): this repository's Git worktrees with branch, HEAD, dirty/untracked, locked and prunable state. Enter stages `cd -- ''` in an empty composer (it never runs it and never replaces a draft). `n` takes a branch name and shows the exact plan; nothing is created until Enter on that plan, and Git state is revalidated first. `x` previews removal; removal is never forced, keeps the branch, and is refused for the main, current, locked, prunable or dirty worktree and while a managed agent works inside it. +- **`/github`, `/prs`, `/issues`** ([PR #343](https://github.com/raiseCatError/notMyShell/pull/343)): a read-only workspace for the `origin` repository through your own `gh` login: PR and issue lists with GitHub search syntax, PR overview, checks (with required flags), reviews and comments, commits, files and a bounded diff, and a merge *preview* that only describes what a merge would do. `o` opens the item on github.com and `w` jumps to the PR branch's worktree. +- Ask knows `/worktrees`, `/github`, `/prs` and `/issues`. + +#### Terminal and shell ([#304](https://github.com/raiseCatError/notMyShell/issues/304)) +- **Terminal title** (Settings → Sessions, Off by default): Project, or Project and session, written only while NMSh owns the screen. Programs keep their own titles, and hosts with a title stack get their previous title back on exit. +- **Open in pager** block action: a command and its complete stored output in your pager on the host terminal, through stdin only (never shell source), with escape sequences shown inert. +- Typo correction works in Bash and Fish, and Bash/Fish command recognition, correction and install offers use the live shell's `PATH`. +- delta's status says whether its syntax highlighting follows bat's NMSh theme or your git config pins it (read as data); diff colors stay in git config. + +### Changed +- **One owner for strip facts:** the strip's clock, CPU, RAM, battery and uptime are Context Engine capabilities (new `system.cpu` and `system.uptime`), demanded only while the row shows and only for items switched on. The strip's separate stats poller is removed, and painting the strip performs no I/O. +- **Tab / Shift+Tab** switch tabs in every visual tab bar (`/prompt` views, Modules, Settings, Tools, tmux configuration, Theme Studio), wrapping at the ends; text fields keep Tab. +- Without color (NO_COLOR or Color None), the active tab in every NMSh tab strip is reverse video, matching selected rows; before, only its weight changed. +- Host semantics: command start (OSC 133 `C`) is written at once, even ahead of a fullscreen program; the directory is reported again after a program owned the screen; a reattach replays no history to the host. +- Renderer **Portable** leaves NMSh's managed Ghostty cursor shader off (NMSh draws the trail itself), and the managed shader files follow `/cursor` inside tmux. +- Context Engine benchmarks (cold/warm collection, rapid directory changes, every module on every surface, hidden modules, a large repository) with budgets enforced by CI's timing smoke. + +### Fixed +- **Absolute executable paths** typed in the composer (`/Volumes/tools/adb devices`, `/bin/zsh`, `/usr/bin/env …`) run in the shell exactly as typed instead of being refused as unknown NMSh commands, and keep shell completion and suggestions. A first word with a `/` after the leading one is a path (no NMSh command name contains one), so `/zsh` still switches shells while `/bin/zsh` runs; a directory used as a command gets the shell's own behavior. +- **`/copy`** and `/copy N` copy only the command's own output (stdout and stderr, line breaks and spacing kept), never NMSh's completion row such as `✔ Completed · 21.6s · 15:42`, nor NMSh lines (such as a `/copy` confirmation) added inside a running command's block; output that merely looks like a completion row is kept. A command that printed nothing leaves the clipboard as it was and says so. +- **Folding after a screen clear:** after `clear`, tmux, or any program writing `ESC[2J`/`ESC[3J`, fold rows such as `15 lines shown · Ctrl+O` no longer appear inside the next command's output, and Ctrl+O no longer folds another command's lines together with `/copy` and status rows. A clear now retires the records of the lines it removed; a command that clears the screen owns, and `/copy` copies, what it writes afterwards. Transcripts saved before this fix are repaired when opened: a record is kept only if its lines show it owns them, and older commands from before an unrecorded clear lose their fold control and `/copy N` while their lines stay visible. Full-screen programs, partial erases (`ESC[J`, `ESC[1J`, `ESC[K`) and progress redraws never clear the transcript. +- **Fish keystrokes around `read`:** keys typed quickly around a fish `read` are no longer lost to NMSh's query round trip; NMSh answers fish's terminal queries itself, as it already did at fish's prompt, and input waits until fish has redrawn its prompt. For any program, keys never overtake the answer to a device-attributes query. +- **Passthrough programs get their bytes exactly as written:** output processing (`ONLCR`) is off while a program owns the terminal, so raw-mode TUIs that move down with a bare LF no longer overwrite their own prompt or jump the cursor. Two-byte Alt+key prefixes are no longer held as possible terminal answers. +- Terminal queries a program writes before it takes the screen (synchronized output, grapheme clustering, colors) reach the host, and the answers go back to the program instead of the key decoder. +- Ownership handoff: output from the first sign a program owns the screen (alternate screen, an input mode, a kitty keyboard push) belongs to the program, so its paint no longer stays in the transcript after it exits; undrawn sequences no longer print debris (`ESC ( B` → `B`, `ESC[NaN;2m`), zero color components are kept, and half-decoded input is dropped at every handoff. +- **tmux:** `tmux` typed in the composer opens ordinary panes again (tmux no longer inherits NMSh's private `ZDOTDIR` or `POWERLEVEL9K_DISABLE_PROMPT`, and children get the person's own `XDG_CACHE_HOME` back), and a window says when its session service is an older build that still writes the old bootstrap. The mouse wheel scrolls the NMSh transcript instead of cycling composer history (NMSh now asks tmux for button and SGR mouse reports, which tmux forwards with its `mouse` option on or off). NMSh no longer wraps frames in its own DEC 2026 synchronized-output block inside tmux, and repaints that leave the caret in place no longer toggle cursor visibility, so host cursor trails and blink stop jumping. +- **Terminal title** (from the Ghostty QA pass, [#321](https://github.com/raiseCatError/notMyShell/issues/321)): Project and session titles show the session's name at once after `/rename` (and its signature after a reset); after Vim or another passthrough program sets its own title on exit, NMSh writes its title back as soon as it has the screen. +- Theme Bridge colors are valid on 16- and 8-color terminals (theme ANSI slots instead of 256-color codes for less/man, listings and Vim). +- Apply themes → Follow NMSh / Choose theme themes NMSh's own fzf launches even when fzf's Manual setting is Independent. +- A detached session's spooled prompt events are no longer dropped on replay when the shell's knowledge exceeds 64 KiB. +- Unmodified F1 in its kitty keyboard form (`CSI P`) opens the palette. +- Ask's config edits no longer refuse files in a home folder or project reached through a symlink (macOS `/tmp` and `/var` are symlinks); a symlink leading out of them is still refused. + +### Security +- Context discovery never runs a discovered executable; one inside the current workspace (followed through symlinks) is not even inspected. Hostile-workspace coverage spans control/bidi/OSC payloads, huge, malformed and symlinked files, fake executables, malicious PATH entries, `.envrc`, kubeconfig `exec`, hostile Git metadata and malicious packs. +- Every NMSh display path (prompt, modules, path, Status Strip, `/prompt` and `/modules` disclosures, fact cache, historical prompt blocks, welcome, startup picker, `/resume` and live-session lists, `nmsh packs` and `nmsh agent-status` output) removes escape sequences whole instead of leaving visible debris; an unterminated OSC in a session-reported directory can no longer swallow the startup screen. Hostile directory names in the terminal title lose whole escape sequences instead of showing `]0;PWNED` debris. +- Retained control sequences are bounded: the transcript parser keeps at most 8 KiB of one unfinished sequence (OSC, DCS, SOS, PM and APC are discarded to their terminator), NMSh's shell markers are capped at 1 MiB and the key decoder at 256 bytes. +- Keys held behind a terminal answer are discarded by Ctrl+C before it is delivered, as a terminal discards pending input, so a cancelled line cannot still run; bracketed pastes are never searched, split or reordered by the hold. +- `/github` writes nothing to GitHub and stores no credentials; IPv4 and IPv6 addresses in GitHub error text are shown as `[address]`. + +### Compatibility +- Node.js 22 or newer, as before. CI covers Node 22 and 26 on Ubuntu, Node 26 on macOS, and a Linux portability subset on Fedora. +- zsh, Bash 4.4+ and Fish, as in v0.17.0. +- Managed targets need Claude Code installed. `/github` needs the GitHub CLI (`gh`) logged in and a github.com `origin` remote. + +### Upgrade notes +- Homebrew: `brew upgrade raiseCatError/tap/nmsh` once the tap's formula reaches 0.18.0 (the tap's own automation bumps it). `/update` in a Homebrew install points to Homebrew instead of changing the keg; source checkouts update with `/update` as before. +- Settings carry over: existing prompts, modules and Status Strip placement (Top, right-aligned) are kept, and nothing new is turned on by the upgrade. The terminal title stays Off until chosen in Settings → Sessions. +- Claude Code agent context needs one reviewed settings change from `nmsh agent-status setup`; `nmsh agent-status remove` undoes exactly that change. +- A session service started by an older NMSh keeps writing the old shell bootstrap until it restarts; new windows say when this is the case. + +### Known limitations +- **Physical-terminal QA was not performed for this release.** `/worktrees`, `/github`, the managed agent view and `/mods` are covered by automated tests and headless terminal recordings; the fixes for absolute paths, `/copy`, folding after a screen clear and Fish keystrokes by automated real-PTY tests. None has had a hands-on pass in Ghostty or Terminal.app. The checklist stays open as post-release follow-up: [docs/testing/v018-physical-qa.md](docs/testing/v018-physical-qa.md). Physical checks in iTerm2, Kitty, WezTerm, Linux and WSL 2 also remain open. +- A lone `ESC[3J` (erase scrollback, without `ESC[2J`) empties NMSh's whole transcript, where a real terminal keeps the screen. `clear` and `clear -x` are unaffected. This dates from the first release; the planned fix is to treat a lone `ESC[3J` as clearing nothing NMSh shows. +- Type-ahead while a fish `read` is still running (the answer and the next command in one fast burst) can still be dropped by fish itself, as in any terminal ([docs/development/fish-typeahead.md](docs/development/fish-typeahead.md)). +- Managed targets are Claude Code only. Codex, OpenCode and other agents' running sessions are shown, not controlled, and live attach to a Claude session started outside NMSh is not supported. +- `/mods` is an inventory: enabling or disabling entries is done in the provider (or with `nmsh packs` for Context Packs), and NMSh does not run portable mod code. +- `/worktrees` and `/github` are first slices; [#337](https://github.com/raiseCatError/notMyShell/issues/337) and [#338](https://github.com/raiseCatError/notMyShell/issues/338) stay open. `/worktrees` navigates by staging `cd` in the composer (focusing an NMSh session or tmux pane is not implemented), and the GitHub workspace is read-only: no merging, reviewing, commenting or closing from NMSh. +- Context Packs install from local files only; a remote pack registry, downloads, signature trust and any executable extension tier are not implemented. +- Still deferred from [#304](https://github.com/raiseCatError/notMyShell/issues/304): delta diff styles (git config) and terminal emulator or editor base-theme takeover. + +## [0.17.0] - 2026-10-06 + +Context Engine, Themes & Discovery: native context routing and Rail foundations, Theme Studio and Theme Bridge, shell/transcript refinements, contextual tools and portability hardening. + +### Context Engine and shell/transcript UX +- **Context Engine foundation:** immutable fact metadata (provenance, freshness, trust, sensitivity and persistence), a native module registry, bounded trusted metadata/Git collection, and Surface Router placement across Main Prompt, Context Rail and Right Context. Repository entry never executes arbitrary repository-controlled code through discovery. General capability scheduling, a broader first-party module catalog and installable declarative Context Packs remain upcoming; no finalized pack SDK or executable plugin API is available. +- **Context Rail:** independent relation, direction, integration, spacing and divider-anchor controls; one or two actual content rows; horizontal Inside dividers; priority overflow; shared preview/live geometry. Right shell context retains its independent anchor, and existing custom prompt/module configurations are preserved. +- `/transcript` exposes the same Normal / Chat live presentation setting as `/layout` and Settings, alongside historical prompt and divider controls. +- `/shell` keeps the current row visibly ticked and highlighted independently of keyboard focus, with explicit current/default markers. Shell indicator Hidden / When not default / Always and Left / Right edit the existing prompt module; changing Side preserves a saved Hidden state and never changes the active session. +- `/btw` is the canonical local guidance command; `/ask` remains a compatibility alias of the same feature. +- Portable shell/default-selection coverage, selection-band assertions and canonical CI sharding; full macOS/Ubuntu suites, Node 22 compatibility, Fedora portability and bounded timing smoke cover release candidates. + +### UI consistency and showcase +- Shared bold accent focus labels across menus, settings, pickers and setup; `/tools` keeps full-row semantic selection bands when focus moves, with reverse video without color. +- Consistent Chroma preview state and base-color guidance; `/syntax` exposes the shared theme families and variants, with previews and cache updates for custom colors and accents. +- First launch opens Setup Cat. Apply completes onboarding even without appearance edits; presets and completed onboarding bypass discovery. +- The current-shell module leads the default Native prompt; the former untouched default migrates, while customized module ordering is preserved. +- Larger opaque demo recordings with neutral welcome identity, expanded feature/screensaver coverage, two architecture diagrams and a standalone silent motion promo. + +### Themes, Theme Bridge and host cooperation +- **Prompt provider None**: composer only (no prompt row, modules or right prompt; the input marker stays) while editing, suggestions, syntax colors, history, themes and Theme Bridge keep working; commands submitted under None store no prompt snapshot. +- **Historical prompt** Full / Compact / Minimal / Off (`/transcript`, Settings, Setup); presentation only over the unchanged stored snapshot. +- **Native theme library**: any number of Custom and Imported themes with stable ids (bounded to 64); the single custom theme migrates into it and stays active. Imported is provenance only: imported themes are ordinary Native themes you can edit, rename, duplicate, export, select and pin. +- **Theme Studio** (`/theme`): Built-in · Imported · Custom · Import tabs, one editor and the real Native preview for every theme. Settings → Theme and `/setup appearance` select Built-in, Imported and Custom themes directly. +- **Imports**: NMSh Theme JSON, Base16, Base24, Windows Terminal, Oh My Posh (JSON, YAML, TOML; static colors only), Kitty, Ghostty (allowlist), iTerm2 `.itermcolors` (no XML entities) and WezTerm TOML (Lua refused). Data only, previewed with mapping and loss disclosure before saving; exports never include local source paths. +- **Theme Bridge** (`/theme-bridge`, opt-in, default Off): one switch plus **Apply themes** Manual / Follow NMSh / Choose theme. Under Manual each tool is Independent / Follow NMSh / Choose theme; under a global policy per-tool rows are view-only and the Manual choices are kept for later. One persistent panel with inline rows (Esc collapses before it closes), grouped by capability. fzf launched by NMSh, less/man termcap colors and **File listing colors** (GNU `ls`/`gls` via LS_COLORS with vivid when installed, BSD/macOS `ls` via CLICOLOR/LSCOLORS) through an NMSh-owned shell environment applied by the zsh, Bash and Fish adapters at the next prompt; generated tmux, Neovim, Vim, Helix and bat themes (bat: a real `.tmTheme`, a reviewed `bat cache --build` verified with `bat --list-themes`, `BAT_THEME` through the environment) with an ownership ledger, staged validated writes, a typed tmux reload and exact includes added only after review. delta is shown and not editable. "Remove managed setup" removes NMSh's files and includes; "Set Independent" only stops applying. No rc file, terminal or editor theme, or git config is changed. +- Theme Studio: local **Preview Chroma** (default Off, never saved) and **Duplicate current** into Custom (` - Custom`). Chroma is reachable from `/prompt`, `/appearance` and Setup (Appearance and Prompt, with a Setup-local preview toggle). +- **Host semantics**: OSC 7 working directory and OSC 133 command zones derived from NMSh's command lifecycle on capable hosts and tmux; NMSh-authored OSC 8 links in `/help` and dev-server task rows, kept separate from program links. +- `/appearance` is a compact launcher: Theme Studio, Prompt, Cursor & effects, UI chrome, Chroma, Motion, Theme Bridge and host window. + +### Providers, tools and integrations + +- **Keep Awake** (`/caffeinate`, `/awake`, `/zoomies`; one surface and state): Idle, Display, System and All, optional `30m`/`2h` timeouts, `status` and `stop`. Backends are detected, not assumed: Apple `/usr/bin/caffeinate` (fixed flags; System needs AC power), `systemd-inhibit` with `idle`/`sleep` only around an NMSh-owned wait helper (Display is reported unsupported on Linux), and Windows `SetThreadExecutionState` from a fixed hidden PowerShell helper (no away mode, no `powercfg`). The assertion is a detached process that outlives the window; ownership is a random token plus the exact command line, so an unverifiable record is cleared and nothing is killed. Changing mode asks first (default No) and starts the new assertion before releasing the old. +- Keep Awake **presentation**: while active, `Awake · ` is NMSh composer chrome (never prompt or provider output). Placement **Composer edge** (default) uses a plain top divider, else the bottom divider when a header prompt owns the top edge, else one row next to the composer; **Above composer** and **Input row** (only when it is completely safe; editor width, caret and hit testing account for it) are explicit choices, and a fallback never rewrites the setting. Both edges render through one shared edge renderer, so animated Chroma dividers keep it. An enabled **Status Strip** always includes it (narrowing before it drops); after 30 s without NMSh input an **idle reminder** adds the time and a muted `/zoomies stop`; the **screensaver** shows a small positioned status (default Bottom left). Display is Text, Icon or Icon + text. Off shows nothing. The panel gains Duration and these settings; Ask answers status questions and plans timed, change and stop requests through the same controller. +- Fixed: `/caffeinate`, `/awake` or `/zoomies` without arguments opened a panel that was never drawn, so the composer looked occupied until Ctrl+C (the Mise panel had the same problem). A start or stop from the panel now returns to the composer at once; the assertion was and remains an NMSh-owned background process, never a shell command. + +- **Shell frameworks and prompt engines:** `/tools` detects tools by an explicit strategy (executable or a registered filesystem detector), so Oh My Zsh, Powerlevel10k, Prezto, Zim, zinit and Antidote appear with factual status (`Installed · Zsh framework · used by Zsh only` under Bash/Fish). Detection grants no install, configuration or provider authority. Oh My Zsh has a guided install that keeps `.zshrc` and a `.zshrc.pre-oh-my-zsh` comparison with a reviewed restore; NMSh never runs its installer. Powerlevel10k is a `/tools` item that routes to the existing provider and `p10k configure` flow. **Oh My Posh** is a new Prompt provider (`oh-my-posh print primary`, argv only, no TTY, bounded, cancellable; Native fallback when it fails) and a curated `/tools` install (homebrew/core); its config can be imported into Theme Studio as static colors. Ask understands these requests; dotfiles treats `.p10k.zsh`, Oh My Zsh themes/plugins and Oh My Posh configs as inspect-only. +- `/providers` is one inline panel: each family with its status (● Active, ✓ Selected · fallback, Available, Missing · Enter to install); Enter selects immediately or installs after a confirmation (default No). Shortcuts `/picker` (`/pickers`), `/suggestions`, `/navigation`, `/welcome`, `/history-provider` and `/providers ` open it focused. +- Pickers follow the composer: with the composer at the bottom the query sits at the bottom and results above it (NMSh Native and fzf); at the top the query is at the top. +- **Tool Configuration** (`/configure`, `/tmux`): one first-party registry says which tools NMSh can configure (tmux, Starship), which it themes, and which are inspect-only (shell rc files, Neovim/Vim config). Detection never grants write authority. +- **tmux Config Studio** (`/tmux`): General settings from a documented catalog, keymaps and prefix with conflict display, a Status Studio with a live preview (status modules never run shell), an optional NMSh pane frontend (`default-command` that runs a fixed `/bin/sh` program with the NMSh path passed as quoted argv data, never as shell text, and falls back to your login shell inside NMSh; `default-shell` untouched), and import of a supported subset of an existing tmux.conf (`if-shell`, `run-shell`, `source-file` and `#()` are never followed). Each value shows where it comes from. Everything goes into one NMSh-managed tmux file, included once after review. +- `/integrations`: health of every managed integration (current, missing, stale, conflict) with Review all / Apply all (default No); after one-time activation, managed files update automatically when the theme changes. +- `/dotfiles [path or Git URL]`: plain, Git, GNU Stow and chezmoi sources. Remote sources are cloned only after confirmation (depth 1, no submodules, hooks disabled). Nothing in the repository is run, templates are not rendered, tmux imports supported fields only, Starship/Helix/bat and all executable configs are inspect-only (parseable config can still run commands, so nothing is copied without an explicit per-tool safety validator, and none exists today), conflicting values default to your current ones, and one combined review (default No) precedes any change. The repository is never modified. +- Ask maps tmux, provider, Theme Bridge, integrations and dotfiles requests onto these typed actions only, behind its final Yes/No. +- Commands: `/motion`, `/chrome`, `/glyphs` (`/glyph`), `/composer` (alias of `/layout`), `/strip` (`/status-strip`), `/configure`, `/tmux`, `/integrations`, `/dotfiles`. `/help` groups commands by area (Appearance, Composer & transcript, Providers, Tools & integration) and the palette lists each surface once with a readable label. Individual settings are not commands. + +### Contextual tools +- `/tools` Discover shows a conservative **Relevant here** group from cheap local facts (Git repository, shell scripts, JavaScript/Node, Python, Go, Rust, container files, Kubernetes files or kubeconfig) and each tool's declared relevance. Only missing tools appear; nothing is executed, crawled or sent anywhere. +- Typed package-manager plans for Homebrew, APT, DNF, pacman and zypper (WSL uses the distribution's manager). Tools without a verified package name stay manual. Non-root plans are explicit `sudo -n` argv; nothing elevates silently. +- Bulk install: Space selects missing installable tools, Enter reviews a tool → manager → package plan, one confirmation (default No), per-tool results. +- Tool details separate Installed, selected in NMSh and **Active in this shell** (zoxide, Atuin, fzf), from the running shell's name snapshot for all three shells; rc files are never read. + +### Compatibility +- Windows Terminal (seen from WSL), iTerm2 and WezTerm host facts and new-window launchers behind the host boundary; deterministic multiplexer/`TERM=dumb` degradation tests for every profile. Physical validation in these hosts is not claimed. +- Linux: `xdg-open`/`wslview` open helper, clipboard fallback-order tests, Fish installed in Ubuntu CI and a Fedora portability job. +- Regression coverage that raw job-control, EOF and arrow bytes reach full-screen programs. + +### Screensavers +- Four screen-based savers join the idle visuals: **Black Hole**, **Fireworks**, **Circletastic** and **raiseCatError**, plus **Random** (switches only after a full loop). They animate the current screen's own text as presentation only (transcript, PTY, history and journals are untouched), keep the host background, dismiss on the first input (which is consumed), stop on resize, and never start automatically under Reduced Motion. Still off by default (Idle visuals: Never); new **Run while busy** setting never overrides passthrough or fullscreen programs. The earlier scene is now labelled Night Fireworks. + +### Polish +- Shell Environment (Status and `nmsh doctor`) starts with the shell backing the session (never inferred from `$SHELL`), says "none · plain zsh/bash/fish" when there is no framework, and scopes framework and plugin rows to that shell; other shells' environments are listed separately. +- Screensavers: the capture keeps authored backgrounds and readable glyph colors (no black-on-dark chrome), Circletastic forms a few small circles completely before it stabilizes, rotates, accelerates and explodes (all at once or staggered, keeping ring momentum), and raiseCatError now uses the NMSh cat sprite, roams the whole screen, overlaps text freely, meows, and sometimes sits on a purely visual fake keyboard (never reaching the editor or shell). +- Any exact command in the curated `/tools` catalog (not only Recommended ones) is recognized when missing; the prompt names the package when it differs (`tldr` is provided by tealdeer). TLDR (tealdeer) is now Recommended; Ask uses only its local cache (`--no-auto-update`) and says when examples are unavailable. + +### Polish (this pass) +- `/tools`: the selected row is the shared selected band (the active tab's treatment): full width, bold, readable on the band, reverse video under `NO_COLOR`. +- Prompt None wording: the input marker stays (it always did); help, the prompt picker and Setup now say so. + +### Docs and demos +- [ARCHITECTURE.md](ARCHITECTURE.md): a plain-language overview of how NMSh works, linked from the README, CONTRIBUTING, AGENTS and llms.txt. +- README rewritten around what NMSh is, a GitHub-hosted reel hero with YouTube/X links, a mascot-left-of-logo lockup, a visual tour and the safety model; stale zsh-only, fixed-bottom, bat and release claims corrected. +- Reproducible visual docs: `npm run demos` renders the README and [demo gallery](docs/demos.md) clips from committed VHS tapes in `scripts/demos/` against a disposable demo home (no user config, no network, the inert Keep Awake backend). It replaces the old asciinema/tmux recorder. A small Vespyr divider is generated from the real sprite. + +### Updates and sessions +- **Automatic updates** (Automatic / Notify only / Off, Daily or Weekly). New installs default to Automatic / Daily; saved Daily/Weekly checks migrate to Notify only and Off stays Off. Automatic prepares a verified stable release only where `/update apply`'s own checks pass, with the same build verification and rollback; the running session keeps its version. Status shows Running version, Latest, Mode and State. +- Homebrew install provenance prevents the source-checkout updater from mutating the Cellar. Homebrew distribution is now available through `brew install raiseCatError/tap/nmsh`; automated publishing from this repository remains disabled. +- Detached sessions can be ended from the startup picker with `X` and confirmation; the transcript is archived and stays in `/resume`. + +## [0.16.0] - 2026-10-04 + +Sessions, Agents & Portability: a cumulative release of the work formerly planned as v0.8–v0.15, together with v0.16. + +### Sessions & agents +- Persistent live shells, detach/reattach, session presets and a clearer `/resume` and `/sessions` viewer with factual state, age and duration. +- Short-lived cross-session notices, sticky attention, and familiar session names and accents. +- `/agents` local activity counts, durations and heatmaps; `/ai` managed and observed agent sessions. Activity tracking can be disabled or reset and does not store prompts or output. + +### Shells & portability +- A shared ShellAdapter for zsh, Fish and Bash 4.4+; `/shell` switches the current session and Settings chooses the default for new sessions. +- Linux/WSL groundwork, platform diagnostics, Linux notifications through `notify-send`, host capability profiles and conservative presentation fallbacks. Physical Linux/WSL and additional-host validation remains follow-up work. +- Settings export/import, launcher uninstall with data retained by default, and provenance-aware tool removal. + +### Ask, completion & developer tooling +- Ask explains NMSh capabilities locally, with optional Qwen Local Understanding through `/llm`; model downloads require explicit confirmation. +- Structured completion with live shell knowledge, a bundled Fig + Carapace static catalog, provenance, custom declarative specs and richer completion descriptions. +- Context-ranked history with agent/source filters; optional Atuin, zoxide, fzf and Television providers; command inspection and conservative correction suggestions. +- Curated `/tools`, supported Starship configuration, consent-based mise project/task awareness, project workflows and `/watch` scheduled commands. + +### Appearance & interaction +- `/appearance`, richer prompt customization, Chroma treatments and bounded effects; Clean and Rich motion with independent intensity and speed. +- Portable cursor effects and capability-gated Ghostty/Kitty integration with previews and clear fallback behavior. +- Expanded Setup Cat, grouped Config and Status, idle visual galleries and completion UI polish. + +### Transcript, safety & fixes +- `/find`, presentation-only `/filter`, transcript selection, block actions, OSC 8 links, `/open` references and `/open-diff` editor integration. +- Capability-gated images with a text fallback; deterministic paste classification, preview and scrollable review; `/doctor` read-only diagnostics and failure explanations. +- Hardened session recovery, shell helper isolation, terminal modes, completion cancellation and configuration preservation; fixes to divider colors, slash-command history and presentation lifecycles. + +## [0.7.0] - 2026-10-01 + +UI Foundation & Customization: a shared internal UI toolkit (notMyUI), Chroma color roles, reduced-presentation modes, Markdown-authored help, and Settings v2. + +### Added +- **notMyUI toolkit:** an internal presentation and interaction foundation for NMSh-owned surfaces (not a published package). [docs/architecture/notmyui.md](docs/architecture/notmyui.md) maps each primitive to its real consumers and lists what is deliberately not built. +- **Shared actions and contextual help:** panel and palette actions share one model (identity, label, key, enabled state), and footer help is derived from it, so it stays in step with what a panel can actually do. +- **Shared form controls:** toggles, selects, multi-selects, text fields and confirmations are reusable controls that report proposals while the feature layer persists. Settings rows and Settings search use them. +- **Accessibility baseline:** `NO_COLOR` (or `TERM=dumb`) stops NMSh-generated color escapes while bold, inverse and glyphs remain. `NMSH_COLOR=none|256|truecolor` overrides the color level explicitly. `NMSH_REDUCED_MOTION=1` holds the shimmer and the welcome blink still while durations keep counting. Focus, changed and error states are also carried by text or glyphs, not color alone. See [docs/accessibility/baseline.md](docs/accessibility/baseline.md) for criteria and known gaps. +- **Deterministic presentation:** `NMSH_DETERMINISTIC=1` fixes the displayed completion time, the shimmer and activity phase, and the welcome blink for repeatable captures, and implies reduced motion. Shell behavior, PTY output and measured durations stay real. See [docs/testing/deterministic-presentation.md](docs/testing/deterministic-presentation.md). +- **Chroma:** shared color roles for NMSh-owned UI in three distinct categories (semantic status, theme and identity colors), plus gradients and curves, with truecolor, 256-color and no-color fallback. +- **Surface primitives:** frames, fills, padding, insets, width and alignment, used by the shared panel frame. +- **Semantic motion engine:** a pure engine of motion profiles per state, sampled at an elapsed time. It owns no timers and never changes width. The existing running-command shimmer and activity glyph now run on it. +- **Authored Markdown and `/help`:** `/help` renders NMSh-authored Markdown (headings, tables, code fences, tips, links). Through the transcript, links appear as `text (url)` rather than clickable hyperlinks. +- **Settings v2:** a simple and an advanced view, a changed marker on settings that differ from their defaults, reset of the current setting, search, and a remembered position within a run. + +### Notes +- Authored Markdown applies to NMSh-owned content only. Raw PTY output, transcript command output, `/copy` and archived shell data are never interpreted as authored UI content or recolored. +- Not included: automatic 256-color detection (`NMSH_COLOR=256` selects it), a persisted reduced-motion setting, user key remapping, a shared animation scheduler, Linguist language colors (#176) and transient visual effects (#78). Screen-reader behavior is unverified, and not every surface uses authored Markdown yet. + +## [0.6.0] - 2026-09-29 + +Sessions & Continuity: persistent live sessions you can detach from and reattach to, a Flow composer, and a `/layout` showcase. + +### Added +- **Persistent live sessions:** zsh now runs in a small per-user session service (`nmshd`, started on demand over a private Unix socket), so closing a window **detaches** its shell instead of ending it. Running commands keep going. `exit`, Ctrl+D and `/zsh` still end the session. `NMSH_SESSION_SERVICE=0` runs the shell in-process as before. +- **Reattach:** get back to a detached session from the startup prompt, from `/resume` (LIVE sessions are listed above ARCHIVED transcripts; Enter attaches, and Ctrl+K kills after confirmation), or with `nmsh --attach `. `nmsh --sessions` lists live sessions, and `nmsh --new` always starts a fresh one. A session attached in another window is never taken over. +- **Output while detached:** what a detached session prints is kept (1 MB in memory, then up to 64 MB spooled to disk; `NMSH_BACKLOG_MEMORY_BYTES`, `NMSH_BACKLOG_SPOOL_BYTES`). It is added to the transcript on reattach, with a note of commands that completed and anything that exceeded the limit. Fullscreen apps are repainted at the new window size. +- **Multiplexer interoperability notes:** [docs/architecture/multiplexer-interop.md](docs/architecture/multiplexer-interop.md) records how NMSh behaves inside and around tmux and GNU screen (rendering, resize, job control, fullscreen, paste, and live-session detach when a pane closes), what remains unverified (Zellij), and follow-ups. NMSh stays independent of any multiplexer. +- **Live session status:** `/resume` LIVE rows and `nmsh --sessions` show what each live session is doing, from evidence only: the running command and elapsed time, the foreground process or a known CLI's name (Claude Code, Codex, Aider, …), active or quiet output, *needs attention* when the program sent a terminal notification or bell, fullscreen, the title it set, and the last command's result while idle. Nothing is guessed, and paths under your home directory are shown as `~/…`. +- **`/layout` showcase:** preview composer position (Bottom, Top, Flow) × transcript presentation (Normal, Chat) on sample content through the real renderer, then save and apply live. Also under Config → Layout. Nothing in the preview runs or reaches the transcript, journal or `/copy`. +- **Flow composer:** Config → Composer position → Flow (the command palette's Toggle composer position cycles Bottom, Top and Flow). The prompt and input follow the newest output inside NMSh's document, like a conventional terminal, and scroll with it. Typing while scrolled back returns to them; scrolling alone does not. Menus open below the input, panels pin to the bottom, and Chat presentation and fullscreen passthrough work as before. +- **Startup restore is your choice:** Config → Sessions → Startup restore (Ask, the default; Always; or Never) and Multiple detached sessions (Ask which, or Open all). With one detached session, NMSh asks: Resume, Not now, Always or Don't resume at startup. With several, a picker restores the ones you select: this window takes one, and the others open in new Ghostty, Terminal.app or kitty windows. Where a host can't open windows, NMSh names the `nmsh --attach` command for each. Never only skips restoring at launch; it never ends a session. +- **Live-session hardening:** live sessions that end while no window is attached are archived at the next launch or `/resume`, with the real exit code or a note that the service stopped or the system restarted. Output captured while detached is kept. A frontend that loses its service reports it and archives the transcript. `/resume` shows how long each idle live session has been at its prompt. +- **Exactly-once archiving:** launch recovery, Kill Session and live journal checkpoints share a cross-process lock per journal, so concurrent launches never archive a session twice or overwrite a complete archive with partial state. +- **Fullscreen reattach:** reattaching to a running fullscreen app restores the terminal modes it had turned on, including mouse reporting and bracketed paste. +- **Safe updates:** each session-service protocol version has its own socket, so live sessions owned by an older service keep running after an update. A newer frontend never touches sessions it cannot verify, and it tells you they exist. +- **Session limit:** at most 16 live sessions per service (`NMSH_MAX_SESSIONS`). When the limit is reached, a new window falls back to an in-process shell with a notice. Detached sessions are never ended to make room. + +### Fixed +- **Interactive terminal UIs that draw inline** (agent CLIs such as Claude Code, including under a wrapper or alias like `claude-account2`) now get the terminal. NMSh hands over as soon as a running program turns on terminal input modes (bracketed paste, mouse, focus events or the kitty keyboard protocol), not only when it switches to the alternate screen or has a known name. Keys reach the program, NMSh's composer steps aside, no control sequences leak into the transcript, and the same applies after reattaching. Ordinary commands, including progress output, stay in NMSh. +- **Restoring several detached sessions** (the startup picker or Open all) opens every selected session again. NMSh used to exit while waiting for the first extra window to open, printing "Detected unsettled top-level await": only that window appeared, and the current window never attached. In Terminal.app this looked like `nmsh` failing to start. A window launcher that hangs now times out after 15 s, and that session's `nmsh --attach` command is named instead. +- Ctrl+Z suspends the foreground job again (raw `^Z` and the Kitty keyboard encoding), and `jobs` and `fg` work as in plain zsh. +- After Ctrl+Z, `jobs` could list a stray `suspended (tty output)` job. NMSh's own prompt hook ran `stty` as a job, and it could be stopped when it ran before zsh had taken the terminal back. The hooks now change terminal modes outside job control. +- The session service no longer crashes when a window resize races a shell's exit. node-pty could throw `EBADF` for a PTY that had just closed, which inside nmshd would have ended every live session. Other resize failures are now reported to that window instead of ending the service. +- A launch notice (for example, sessions archived while no window was attached) is no longer erased when the window reattaches to a live session. + +### Removed +- `scripts/pty-history-smoke.mjs`: its checks had gone stale (it asserted retired UI text), it ran against the real config and live session service, and deterministic tests now cover everything it checked. + +## [0.5.0] - 2026-09-28 + +Interaction & Intelligence: predictive suggestions, new layouts and presentations, a command palette, and a shared provider framework. + +### Added +- **Suggestions v2 (NMSh Native):** ghost text ranked by fuzzy and acronym matching, frecency, the current directory, and what usually follows the previous command. → / End accepts, Alt+→ accepts the next word, Ctrl+N / Ctrl+P list ranked alternatives, and Esc dismisses. Empty-prompt prediction is optional (Config → Empty-prompt prediction). Leading-space and `HISTORY_IGNORE` commands are never suggested or learned, and everything stays local. History loads in the background from Atuin or `$HISTFILE`. +- **Optional Deja suggestion provider:** uses an installed [Deja](https://github.com/Giammarco-Ferranti/deja) through its query CLI while NMSh draws the ghost text. Deja keeps recording through its own zsh hooks. If it is missing or unhealthy, NMSh falls back to Native with a notice. +- **Welcome providers:** Vespyr, the native cat (default), Fastfetch using your installed configuration, Neofetch (legacy, used only if already installed), or None. New sessions and `/clear` use the selection, and `/resume` keeps each session's original welcome. +- **Shared provider framework:** Welcome and Suggestions providers show installed, missing, version, and health status with a preview in one gallery (`/settings`). Installs run only after explicit confirmation, and unavailable providers fall back safely. Prompt providers use the same descriptors. +- **Dock Top:** Config → Composer position: Bottom (default) or Top. With Top, the composer and its menus sit at the top, and the newest output and live activity stay together at the transcript's end. +- **Chat presentation:** Config → Transcript presentation: Normal or Chat. Submitted commands align right with their prompt snapshot and a divider local to the command, and output stays left. It works with either composer position and falls back to Normal below 60 columns. Stored transcripts and `/copy` are unchanged. +- **Output folding modes:** Off, Smart (default), and Always. Always folds every long block, failures included, keeping head and tail visible. Existing `never` settings load as Off. +- **Command palette:** `/palette`, F1, or Ctrl+Shift+P / Cmd+Shift+P where the terminal reports them. It searches NMSh commands, settings, layout toggles, and prompt themes, runs only NMSh actions, and never runs shell text. +- **Native prompt styles:** Powerline (default), Soft, Minimal, and Outline (`/prompt` → Style or Config → Prompt style), with Nerd and Safe glyphs. History snapshots keep the style they were captured with. + +### Changed +- Transcript rows are drawn by a single presenter, so layouts and presentations share one row pipeline without changing stored data. + +### Fixed +- NMSh-owned Homebrew installs (Starship, Fastfetch, Deja) now find `brew` in its standard Apple Silicon and Intel locations when NMSh's PATH lacks it, and report clearly when it is missing. +- Right-aligned Chat commands no longer lose their final character at the terminal's last column. +- The command palette list stays put while moving the selection and scrolls only at its edges. + +### Known limitations +- Completion, richer History, and Picker providers are future work. Configured-zsh completion parity and `fzf-tab` remain unsupported (#52). +- Flow / Classic composer mode and persistent live sessions are not part of this release. +- Deja is optional and is not bundled. Neofetch is archived upstream and is never installed by NMSh. +- NMSh renders with truecolor. There is no separate 256-color mode. +- Command-completion desktop notifications are not part of this release. +- Final v0.5.0 visual and interaction validation passed in Ghostty. + +## [0.4.0] - 2026-09-26 + +Shell Intelligence & Extensibility: a richer, context-aware prompt, calmer command output, and a settings, session, and update foundation. + +### Added +- **Right-side prompt context:** in `/prompt` → Modules, `P` moves any module (Project, Path, Git branch, Git status, Toolchains, Exit status, Kubernetes, Docker context) between the left prompt and a right-aligned area. By default everything stays left, and the left side may be empty. The right side works in every composer layout, yields first on narrow terminals, and is recorded in history. +- **Mirror right side** (`M`, default On) reflects the right area's geometry to face left while module order, colors, and Previous/Next fade semantics stay the same. +- **Deterministic prompt showcase:** the Modules, layout, and appearance previews render every module type from synthetic data through the real prompt renderer, independent of the current directory. +- **Show-on-command modules:** Kubernetes (current kubectl context, for `kubectl`, `helm`, `k9s`, and similar) and Docker context (for `docker` and `docker-compose`) appear only while such a command is typed. Toolchains can also be set to *on command*. Typed text is only tokenized, never executed, and context comes from cached asynchronous reads of kubeconfig and the Docker CLI config. +- **Intelligent directory shortening:** as the terminal narrows, the path abbreviates parents to their shortest unambiguous prefix, then directories inside the repository, then collapses runs to `…`, and finally shows only the current directory. The repository name and current directory stay whole, `~` stays `~`, and history keeps the full path. +- **Richer Git prompt state:** one segment each for staged, modified, untracked, conflicts, ahead, behind, diverged, and merge/rebase/cherry-pick, plus a small clean-tree marker; an unknown status never shows as clean. Rich Git state is its own **Git status** module next to the branch. The **Rich Git** view in `/prompt` sets Enabled, Colors (Semantic / Follow theme / Grayscale), Geometry, and Connector fade, with a showcase of every state. +- **Connector fade and Gap Wide:** the opening cap into the next segment can fade (Off by default, Follow connector, or a fixed shape), Fade colors choose Previous, Next, or Mixed, and Gap gains Wide alongside Off, Compact, and Normal. +- **Theme-aware syntax highlighting** and a `/syntax` panel: Highlighting On/Off, and Colors Follow prompt theme / Choose theme / Grayscale, with live previews. Submitted commands keep their original styling, and PTY output and `/copy` are unaffected. +- **Smart output folding:** finished commands fold only when their output is long and low-information (repetition, progress rewrites, install/build/test chatter). Failures, output of 30 lines or less, diagnostics, stack traces, compiler/test errors, diffs, and commands whose output is the result stay expanded. A folded block keeps its first 3 and last 5 lines around `› N lines hidden · Ctrl+O`. It is presentation only: `/copy`, the session journal, `/resume`, and expansion keep the full output. Config → **Output folding**: Smart (default) / Never. +- **Sticky command headers:** while scrolling, the command that owns the top of the viewport stays pinned as a one-row header; clicking it jumps to the real header. +- **Central `/settings`:** `/settings` and `/config` open one tabbed panel on Config, and `/status` opens it on Status. Settings holds entry points to the Appearance, Glyph style, Prompt, Transcript, Syntax, and Keyboard panels, Status shows build, shell, prompt, and journal facts, and Config is a searchable list of settings edited in place. +- **Shared panel shell:** every NMSh panel uses one temporary top boundary, title, optional tabs and search, and a context-aware key bar. Panel chrome is never stored in the transcript, `/resume`, or `/copy`. +- **Glyph compatibility choice:** first run offers Nerd Font and Safe/ASCII previews and persists the choice. It can be changed in Config → Glyph style; `NMSH_ICONS` overrides it per process. Existing v0.3 configurations keep Nerd Font styling. +- **Continuous session journal and advanced `/resume`:** the presentation session is checkpointed during use, so it survives interruption. `/resume` groups retained sessions by day, searches command text, project, cwd, and date, and navigates weeks (←/→) and months (Shift+←/→). 1000 unpinned sessions are kept by default. +- **Reusable task progress:** long-running child processes show elapsed time, a per-character travelling shimmer, factual success or failure, and bounded details (`D`/Enter). Starship's Homebrew installation uses it. +- **Starship module editor:** `/prompt` → Starship → Configure modules toggles supported modules and previews the exact lines Starship's own CLI would change. It asks for confirmation, backs up an existing `starship.toml`, and preserves comments and unrelated keys. +- **Powerlevel10k configurator handoff:** `/prompt` can run the official `p10k configure` wizard on the real terminal after backing up `~/.p10k.zsh` and `.zshrc`, and reports which files changed. +- **Update discovery and `/update`:** `/update` reports current → available with a release summary. `/update apply` fast-forwards a clean official source checkout to the verified release tag, rebuilds, verifies the build identity, and rolls back on failure; other installations get manual steps. Background checks are opt-in (Config → Update checks: Off / Daily / Weekly). + +### Changed +- Connector fade defaults to Off; explicitly saved values are kept. +- Gap is stored as width 0 / 1 / 2 (Compact / Normal / Wide); legacy widths above 2 read as Wide. + +### Fixed +- Compatible zsh `precmd`/`preexec` hooks from your own config (for example zoxide and Atuin) keep working inside NMSh (#16). +- A zsh prompt theme such as Powerlevel10k no longer paints its own prompt, including its right-side status, into command output. +- Esc in Prompt, Transcript, and Keyboard panels opened from `/settings` returns to `/settings`, and Esc is decoded correctly under Ghostty's Kitty keyboard protocol. + +### Known limitations +- Configured-zsh completion parity and `fzf-tab` interoperability are not supported yet; NMSh uses its own completion (#52). +- Command-completion desktop notifications are not part of this release. +- Powerlevel10k: NMSh shows only its left prompt. The right prompt, gitstatus daemon, instant prompt, and settings defined only in `.zshrc` are not reproduced. +- Nested activity is limited to directly observed Node TAP v13 streams. +- `/update apply` automates only clean source checkouts of the official repository. +- Final v0.4.0 visual and interaction validation passed in Ghostty. + +## [0.3.0] - 2026-09-24 + +Session & Interaction UX, plus the appearance and customization system. + +### Added +- **Prompt providers:** NMSh Native (default), optional Starship, and optional Powerlevel10k. Starship and Powerlevel10k supply prompt content only; NMSh keeps the editor, composer, history, and structured execution. `.zshrc`, `starship.toml`, and `.p10k.zsh` are never written. +- **Powerlevel10k** renders your left prompt in an isolated helper zsh (no ZLE, no controlling TTY). Without its prompt character, git state comes from p10k's `vcs_info` fallback, and there is no right prompt yet. +- **Native prompt themes:** Lavender Native (default, brand lavender `#A67CF3`), Brand / Semantic, Cool First, Warm First, and Grayscale, with per-theme previews in `/prompt`. +- **Native geometry:** independent Start, Connector, Gap (Off / Compact / Normal), and End. Shapes are Wedge, Flat, Rounded, Slant `/`, and Slant `\`, with fading variants on outer edges only. +- **Native icons** On/Off for git and toolchain modules; toolchain modules for Node, Go, Python, and Docker are detected from marker files. +- **Module manager** in `/prompt`: show/hide, reorder, and the exit-status condition. +- **Composer layouts:** two-line with the prompt row as divider, two-line inside a bordered composer, or one-line (existing `composerLayout` and `placement` keys). +- **Semantic prompt history:** each command keeps a snapshot of its prompt; history renders a muted version of it. +- **`/transcript`:** divider and historical prompt On/Off, history colors (Follow prompt / Choose theme / Grayscale), and Compact/Normal divider density. +- **Persistent local sessions:** `/clear` archives the transcript and starts fresh on the same live zsh; `/resume` restores archives. +- **`/zsh`** hands off to an ordinary interactive zsh; nested NMSh is prevented. +- **Rich paste atoms** for large multiline pastes, submitted with exact source. +- **Nested activity rows** for directly observed Node TAP streams, with a per-character traveling shimmer. +- **Welcome header** with a terminal-native full-body cat (occasional blink), a bold `notMyShell` wordmark, build identity, start cwd, and shell. +- **Build identity** via `/version` and `--version`. +- **Settings panels** share a consistent controls row. + +### Changed +- `startStyle: "pointed"` is now `wedge`, and Start no longer affects internal segment openings; existing configs normalize automatically. +- The retired Soft Semantic theme id (`semantic`) normalizes to Brand / Semantic. + +### Fixed +- Prompt settings previews no longer change the live provider before saving, and a failed external provider now reports that NMSh Native is active. +- Wide Starship prompts in history are truncated to the row width. + +### Known limitations +- Native `fzf-tab` support does not exist yet; configured-zsh completion parity is tracked for v0.4 (#52). +- Powerlevel10k: the right prompt, gitstatus daemon, instant prompt, and settings defined only in `.zshrc` are not reproduced. +- Nested activity is limited to directly observed Node TAP v13 streams. +- Final v0.3.0 candidate visual and interaction validation passed in Ghostty, Terminal.app, and VS Code integrated terminal. + +## [0.2.0] - 2026-09-23 + +### Added +- **Structured Execution Presentation**: Dynamically classifies PTY output into `INLINE`, `FOLDED`, `LIVE`, or `PASSTHROUGH` modes based on output characteristics. +- **Factual Command Milestones**: Real-time status now states factual observations (e.g., `Running npm test`) and explicitly reports exit codes. +- **Deterministic Summaries**: Output adapters parse standard CLI tools (`npm`, `jest`, `mocha`, Node TAP, `brew cleanup`, `git commit`) to display rich, structured summary facts. +- **Output Folding**: Clickable and keyboard-navigable (`Ctrl+O`) toggles for collapsing or expanding noisy terminal output blocks in history. +- **Native Selection Preservation**: Shift+Drag natively selects terminal text while NMSh handles editor focus. + +### Fixed +- Multiline shell commands no longer break the live-status renderer and clear correctly from the frame upon completion. +- Node.js TAP test summaries are accurately parsed matching the actual `ℹ` info glyphs emitted by the PTY. +- `SemanticService` is robust against host-specific terminal scripts (e.g. `Apple_Terminal` in macOS `.zshrc`) by isolating its environment with `TERM=dumb`. +- `SemanticService` gracefully prevents deadlocks by safely settling pending classification promises if the helper crashes or exits. + +## [0.1.0] - 2026-09-23 + +### Added +- Persistent real zsh PTY frontend. +- Multiline editor locked to the bottom of the terminal. +- Semantic syntax highlighting via asynchronous helper processes. +- Semantic highlighting is preserved persistently in the history viewport. +- Autocomplete and completion bridge. +- History ghost suggestions. +- Lifecycle and animated activity UI for running commands. +- Fullscreen and interactive application passthrough handling. +- Integrated `/copy`, `/history`, `/appearance`, and `/keyboard` commands. +- Visual README demo generation pipeline. +- Controlled zsh bootstrap preventing aggressive startup tasks. +- Machine-readable discoverability configuration (`AGENTS.md`, `llms.txt`). + +### Fixed +- Process cleanup teardown to safely kill detached helper shells and delete temporary configuration directories during testing. + +[Unreleased]: https://github.com/raiseCatError/notMyShell/compare/v0.20.0...HEAD +[0.20.0]: https://github.com/raiseCatError/notMyShell/compare/v0.18.0...v0.20.0 +[0.18.0]: https://github.com/raiseCatError/notMyShell/compare/v0.17.0...v0.18.0 +[0.17.0]: https://github.com/raiseCatError/notMyShell/compare/v0.16.0...v0.17.0 +[0.16.0]: https://github.com/raiseCatError/notMyShell/compare/v0.7.0...v0.16.0 +[0.7.0]: https://github.com/raiseCatError/notMyShell/compare/v0.6.0...v0.7.0 +[0.6.0]: https://github.com/raiseCatError/notMyShell/compare/v0.5.0...v0.6.0 +[0.5.0]: https://github.com/raiseCatError/notMyShell/compare/v0.4.0...v0.5.0 +[0.4.0]: https://github.com/raiseCatError/notMyShell/compare/v0.3.0...v0.4.0 +[0.3.0]: https://github.com/raiseCatError/notMyShell/compare/v0.2.0...v0.3.0 +[0.2.0]: https://github.com/raiseCatError/notMyShell/compare/v0.1.0...v0.2.0 +[0.1.0]: https://github.com/raiseCatError/notMyShell/releases/tag/v0.1.0 diff --git a/website/src/content/reference/configured-completion.md b/website/src/content/reference/configured-completion.md new file mode 100644 index 00000000..bb23dc99 --- /dev/null +++ b/website/src/content/reference/configured-completion.md @@ -0,0 +1,77 @@ +--- +title: "Configured completion" +description: "Canonical configured completion from the v0.20.0 published source." +group: Technical reference +order: 107 +source: "docs/design/configured-completion.md" +--- + +v0.11 extends the existing CompletionSource and CompletionService. A lazy helper +loads the same trusted home zsh startup files as the managed shell, once per +helper generation. It runs detached, with its own hidden zpty; its terminal bytes +never reach the frontend. A private widget receives BUFFER/CURSOR from private +files and invokes `_main_complete` directly, bypassing foreign Tab widgets. +Only bounded NUL-delimited compadd data is returned. NMSh quotes, renders and +inserts candidates; completion never submits a command to the managed shell. + +The helper is reused, expires after 60 seconds, and restarts on cwd changes, +explicit invalidation, cancellation, timeout or failure. Startup is bounded to +1500 ms; warm requests to 300 ms; output to 1 MiB and 4096 candidates. Unsupported +replacement contexts and failed helpers fall back to the existing native source. +Typing remains asynchronous. Cursor, cwd, buffer and service generation identify +requests. No history is written. Configuration is trusted executable code, not a +sandbox; helper isolation protects terminal ownership and managed-shell state. +Both transports also enforce deadlines inside their zsh parents, so abrupt +frontend death cannot remove the only timeout. Hidden shells are launched with +`exec` through zpty, making the recorded PID the inner process-group leader. +Cancellation kills that group explicitly before removing its private root. +Native capture uses nonblocking reads and printable per-query framing; configured +capture keeps bounded NUL records in private files. Suite-root leak checks include +both completion and native capture roots. +Config may still have external side effects. No new frontend config evaluation +or runtime dependency is introduced. + +Failures enter a five-second configured-provider cooldown. Warm cancellation +uses a one-second cooldown to prevent executable configuration reloads on each +subsequent edit; native fallback remains available. Superseding input +requests share bounded startup and serialize queries; canceled requests cannot +return candidates. A warm-query cancellation ends the helper, while a canceled +startup request leaves the shared initialization available to the newest input. +Explicit disposal ends initialization. Each completed managed-shell +command invalidates the helper. Live definitions added only to the managed shell +are not automatically replicated into the helper in this first bridge version. +Unsupported nested syntax, multiline and control-bearing input falls back; +middle-of-buffer native fallback is suppressed because the legacy helper has +no safe replacement protocol there. Configured candidate expiry is checked +before insertion. The frontend never blocks waiting for the helper. + +Protocol semantics follow the upstream [completion widget documentation](https://zsh.sourceforge.io/Doc/Release/Completion-Widgets.html) +and [zpty lifecycle documentation](https://zsh.sourceforge.io/Doc/Release/Zsh-Modules.html#The-zsh_002fzpty-Module). + +Descriptions, groups and compadd prefix/suffix data are mapped into existing +candidates. Callback-based suffix removal and arbitrary ZLE state changes are +outside v1. Native fzf-tab is unsupported. The existing optional fzf picker may +select structured candidates through its normal terminal handoff. + +Implementation plan: first deterministic bridge/parser and lifecycle fixtures; +then bounded persistent transport; then composer cursor/invalidation integration +and picker selection; finally failure, quoting, Unicode, performance and cumulative +verification. Use the current checkout and one primary implementer. Physical QA +is additive and deferred. Version stays 0.7.0; PR targets #266 and stays unmerged. + +## Knowledge parity (v0.15 pass) + +The helper now asks zsh for plain group explanations +(`zstyle ':completion:*:descriptions' format '%d'`) and captures the `-X` +explanation as the candidate group, so groups read "common commands", +"external command", "local directory" or "option" instead of zsh's internal +`-default-`. Internal `-name-` groups are dropped. `_describe` display strings +(`checkout -- checkout a branch`) are reduced to the description. Subcommand +groups after the command word are classified as subcommands, and "external +command" maps to the executable identity. + +zsh-completions is consumed as knowledge whenever it is on the configured +`fpath`; nothing extra is sourced. fzf-tab is detected and reported (Setup Cat, +tools step) but never owns NMSh's editor or completion UI: the helper removes +any `compadd` override and calls `_main_complete` directly. NMSh does not claim +fzf-tab compatibility. Partial user input is never executed. diff --git a/website/src/content/reference/context-modules.md b/website/src/content/reference/context-modules.md new file mode 100644 index 00000000..527635ca --- /dev/null +++ b/website/src/content/reference/context-modules.md @@ -0,0 +1,300 @@ +--- +title: "Context modules and packs" +description: "Canonical context modules and packs from the v0.20.0 published source." +group: Technical reference +order: 103 +source: "docs/architecture/context-modules.md" +--- + +NMSh runs around a real persistent zsh, Bash or Fish session. Its native modules +present useful context about that session and workspace — the project, Git, +runtimes, cloud and cluster contexts, the machine, an AI agent's session — +without taking over shell execution or running anything the workspace chose. + +> **Release status.** v0.17.0 shipped the foundation: typed fact metadata, +> native module routing and the Context Rail. Everything else described here — +> capability scheduling, the first-party module catalog, declarative Context +> Packs, Status Strip routing and Status Strip 2.0, `/modules` and Claude Code agent context — is in +> the v0.18 development stack, pending acceptance and release. + +## From capability to presentation + +```text +shell lifecycle (prompt, command end) / editor demand / explicit refresh + → Context Engine: trusted core capabilities, scheduled on demand + → typed facts (value + provenance, freshness, trust, sensitivity, persistence) + → modules: native built-ins and declarative Context Pack modules + → Surface Router + → Main Prompt / Right Context / Context Rail / Status Strip +``` + +| Concept | Responsibility | +|---|---| +| Capability | A named operation implemented and audited in NMSh core, such as "the nearest project manifest" or "the active Kubernetes context". It declares what it reads, which environment variables it may see, its cost, timeout, cache lifetime and the privacy class of its fields | +| Fact | One resolved value with its source and evidence, collection time, freshness, trust, sensitivity, persistence policy and resolution cost | +| Module | A presentation definition: the facts it uses, visibility condition, show-on-command words, priority, icon, semantic role and preferred surface | +| Surface Router | Places each module from its definition and your saved choice, without collecting data | +| Context Pack | Installable **data** describing modules over core capabilities. It cannot run anything | + +## Facts + +Every fact carries, besides its value: + +| Field | Meaning | +|---|---| +| Source | The capability and its evidence (for example `package.json`, `~/.kube/config`) | +| Collected / freshness | When it was resolved; **fresh**, **stale** (last known, refreshing) or unknown — never presented as fresh when it is not | +| Trust | Where it came from: the live shell session, local metadata, a fixed system tool | +| Sensitivity | `public`, `private` or `secret` | +| Persistence | `snapshot-safe` (may be kept with a command's prompt in the transcript), `display-only` (live surfaces only) or `never-store` | +| Cost | `cheap`, `bounded-async` or `probe` | + +Field policies refine this per field: for example an Azure subscription name is +snapshot-safe while its subscription and tenant ids are private and display-only. +Secret-class facts are never presented; display-only facts never enter prompt +snapshots, journals or exported settings. Every fact value crosses one +sanitization boundary (no controls, bidi formatting, prototypes, functions or +unbounded data) and every displayed string crosses the display boundary. + +## The engine: demand, scheduling and caching + +- **Demand-driven.** Only capabilities that a visible, routed module needs are + resolved, and only the fields it uses. Hidden modules, modules on a surface that + is not shown, and show-on-command modules whose command is not being typed + cost nothing. +- **Off the typing path.** Collection is asynchronous; rendering reads facts + already in memory and performs no filesystem, process or network work. +- **Scoped cache.** Results are keyed by capability, session, workspace and the + environment variables the capability declared. A directory change stages a + new scope; the old one stays visible until the new one settles (or 150 ms), so + the prompt never flashes empty. +- **Bounded.** At most four resolutions run at once (per family: metadata 4, + system 2, agent 1). Every resolution has a timeout; failures back off + (5 s, 15 s, 60 s) and the last known value stays visible as stale. A + capability's first resolution gets three times its timeout, so a cold start is + not mistaken for a hung read. +- **Cancellation.** Superseded work (rapid `cd`) is cancelled; a late result can + never appear for a scope that is no longer current. +- **Freshness.** Values expire after their capability's lifetime and refresh on + the next demand while showing the stale value. A finished command marks the + workspace, runtime, infrastructure, cloud, Git and agent facts for refresh, + since a command can change any of them. Only visible time-based facts (clock, + session duration, memory, battery, agent status) refresh on a timer. +- **Shell-reported environment.** Each prompt, the live zsh, Bash or Fish + reports an allowlisted set of environment values (for example `PATH`, + `AWS_PROFILE`, `KUBECONFIG`, `VIRTUAL_ENV`) and the mere *presence* of + credential variables. Values outside the allowlist are never sent; credential + values never are. A capability sees only the variables it declared. + +`npm run bench:smoke` measures cold and warm collection, twenty rapid directory +changes, rendering every module on every surface, hidden modules and Git in a +large repository, with generous p95 budgets enforced in CI. + +## Capabilities + +| Capability | Reads | +|---|---| +| `project.package` | the nearest declarative manifest up to the repository root (`package.json`, `Cargo.toml`, `pyproject.toml`, `go.mod`, `deno.json`, `composer.json`, `pubspec.yaml`, `pom.xml`, `Project.toml`, `setup.cfg`) and lockfile names | +| `project.build` | build files nearest the working directory up to the repository root (`MODULE.bazel`/`WORKSPACE`, `settings.gradle`/`build.gradle`, `pom.xml`, `CMakeLists.txt`, `meson.build`, `Makefile`), `.bazelversion`, the Gradle and Maven wrapper properties' `distributionUrl` (never fetched), and `cmake_minimum_required`/`meson_version` matched as text. No wrapper, Bazelisk or build file is run | +| `runtime.node`, `runtime.python`, `runtime.go`, `runtime.rust`, `runtime.java` | version pins (`.nvmrc`, `.python-version`, `go.mod`, `rust-toolchain.toml`, `.java-version`, `.tool-versions`, `mise.toml`, …), virtualenv/conda metadata, rustup settings, and the version of the executable the shell would run **from its install layout** (`node_version.h`, `GOROOT/VERSION`, the JDK `release` file, a versioned interpreter path) — never by running it | +| `runtime.ruby`, `runtime.php`, `runtime.dotnet`, `runtime.swift`, `runtime.zig`, `runtime.deno` | `.ruby-version` and the Gemfile's `ruby "x"`, `.php-version` and `composer.json` `require.php`, `global.json` `sdk.version`, `.swift-version` and the first line of `Package.swift` (`swift-tools-version`), `build.zig.zon` `minimum_zig_version` and `.zigversion`, `.dvmrc`, plus `.tool-versions`/`mise.toml`; the active version only from the executable's install layout (Homebrew, mise, asdf, rbenv, RVM, chruby) — never by running it. Floors (`global.json` without `rollForward: disable`, `swift-tools-version`, `minimum_zig_version`) show as `≥x` and are never judged as a mismatch | +| `runtime.elixir`, `runtime.dart`, `runtime.flutter` | `mix.exs`'s `elixir:` requirement (text, never evaluated), `pubspec.yaml` `environment.sdk`/`environment.flutter` (failsafe YAML), `.fvmrc`/`.fvm/fvm_config.json`, `.tool-versions`/`mise.toml`; the active version from the install layout (Homebrew, asdf, mise, FVM, casks) or the SDK's own metadata (`lib/elixir/ebin/elixir.app`, the Dart SDK's `version`, Flutter's `bin/cache/flutter.version.json`) — never by running `elixir`, `dart` or `flutter`. Flutter applies only to a package that depends on the Flutter SDK or pins one with FVM | +| `vcs.hg`, `vcs.jj` | the nearest `.hg` or `.jj` directory found upward from the working directory (stopping at home when below it): `.hg/branch` and `.hg/bookmarks.current`; whether `.jj/repo` is the store or a secondary workspace's pointer and whether Git is colocated. `hg` and `jj` are never run, hgrc/hooks and jj's binary operation log are never read | +| `env.toolVersions` | the `[tools]` table of `mise.toml` or `.tool-versions` lines | +| `env.direnv` | whether `.envrc` exists (never its content) and what direnv itself recorded in the shell (`DIRENV_FILE`) | +| `infra.kubernetes`, `infra.docker` | `current-context` and contexts of KUBECONFIG files (users and `exec` entries are never read); the Docker CLI's current context | +| `infra.terraform`, `infra.helm`, `infra.pulumi` | `*.tf` names and the selected workspace; `Chart.yaml`; `Pulumi.yaml` and the CLI's selected stack | +| `cloud.aws`, `cloud.gcp`, `cloud.azure` | profile, region and SSO metadata from AWS config (the credentials file only for an expiry time); the active gcloud configuration; the default Azure subscription | +| `system.os`, `session.user`, `session.jobs`, `session.duration`, `system.time`, `system.memory`, `system.battery`, `system.cpu`, `system.uptime` | OS release files; user, host and SSH presence (never the client address); the shell's job count; the clock; fixed system tools at absolute paths (`/usr/bin/vm_stat`, `/usr/bin/pmset`) or `/proc` and `/sys` on Linux; kernel CPU counters and uptime through Node (no process). The Status Strip's own items use these same capabilities | +| `vcs.git` | stash depth and upstream from Git's own files, alongside the existing Git branch/status collector | +| `agent.claude` | the private record Claude Code's status line hands to NMSh (see Agent context) | + +Each module's detail view in `/prompt` lists exactly what it reads. + +## What is collected, and what never is + +Collected, locally and only when a module needs it: the files and environment +values listed above, bounded in size and parsed as data (JSON, TOML, YAML with +aliases limited, XML without entities, INI). Nothing is uploaded. + +Never: running a project's scripts or a discovered binary to learn a version; +sourcing `.envrc` or any shell configuration; Kubernetes `exec` credential +plugins; cloud API calls or authentication; reading credential values, tokens +or private keys; broad environment dumps; network access of any kind. + +## Modules + +Native built-ins (Project, Path, Git branch, Rich Git, toolchains, exit status, +Kubernetes, Docker, current shell, discovered tools) keep their behavior. The +first-party catalog adds modules through bundled Context Packs: + +| Pack | Modules | +|---|---| +| Project (`nmsh.project`) | Package; Build (Bazel, Gradle, Maven, CMake, Meson, Make); Node, Python, Go, Ruby, PHP, .NET, Swift, Zig, Deno, Elixir, Dart, Flutter, Rust, Java (show on command) | +| Environment (`nmsh.environment`) | Tool versions (mise/asdf), direnv | +| Infrastructure (`nmsh.infrastructure`) | Terraform/OpenTofu, Helm, Pulumi (show on command) | +| Cloud (`nmsh.cloud`) | AWS, Google Cloud, Azure (show on command) | +| System (`nmsh.system`) | OS, user/host/SSH, jobs, session duration, time, battery, memory | +| VCS (`nmsh.vcs`) | Git stash, upstream (in a repository); Mercurial bookmark/branch and Jujutsu repository (start hidden) | +| Agents (`nmsh.agents`) | Claude Code session, Claude Code limits (show on command) | + +Show-on-command modules appear while you type a related command (`kubectl`, +`terraform`, `aws`, `npm`, `claude`, …) and cost nothing otherwise. Cloud, +infrastructure and agent modules are on by default in that mode; the rest start +hidden. Every module has a deterministic preview, works with Theme and Neutral +roles, shows Nerd Font icons or plain labels in Safe glyph mode, and fits by +priority on narrow screens. + +## Surfaces + +| Surface | Purpose | +|---|---| +| Main Prompt | Stable identity and navigation context, such as directory, project and Git | +| Right Context | Independently anchored context at the right edge of the prompt area | +| Context Rail | Live contextual modules attached to the composer, with their own geometry and priority fitting | +| Status Strip | One persistent row at the Top or Bottom of the NMSh pane with Left, Center and Right groups; routed modules carry a strip group and outrank the strip's own items when space runs out | + +Choices per module: **Auto** (the module's preferred surface), **Main Prompt**, +**Right Context**, **Context Rail**, **Status Strip** where the module supports +it, and **Hidden**. Explicit choices win; saved left/right placement is kept; +upgrades never move existing modules. + +The Rail can be **Vertical** or **Right of Prompt**, with one or two content rows. +Relation, Direction, Integration, Spacing and Divider Anchor are independent +presentation choices. Theme and style can follow the Main Prompt or use supported +Rail choices. Priority fitting compacts or drops whole module groups to fit. Rail +mode **Auto** reserves no rows without visible routed content, **Always** reserves +the configured rows, **Off** reserves none. The Rail is Native presentation: +external prompt providers and Prompt None suppress it (keeping its settings), +fullscreen programs hide it, and it is never written to the transcript. + +## Configuring modules + +`/prompt` → Modules has three tabs: + +- **Modules** — what is in use, in prompt order: Space show/hide, Shift+↑↓ + reorder, ←→ option, P side, S surface, M mirror right side, Enter details. +- **Catalog** — every module grouped by category, with modules suggested by + local evidence under **Recommended here**. +- **Packs** — bundled and installed Context Packs with their verified state; + installed packs can be enabled, disabled or removed (with confirmation). + +Short detail panels keep the module title visible and scroll through existing rows +with Up/Down or PageUp/PageDown. Resize clamps that temporary view; scrolling +does not edit settings or demand data. + +A module's details show what it reads, whether its value may be kept with +commands in history, and each fact's current state ("fresh · 3s ago", "last +known, refresh waits for demand", "unavailable (timeout)", "not checked"). Trusted source IDs, +scope, freshness TTL, event invalidation and demand-driven refresh policy come +from the capability definition. Failure text excludes raw exception paths and +values. A failed refresh can retain a previous value; the view distinguishes +that from a failure with no cached value, without claiming that new data arrived. + +"Took" measures the last collection's wall time, starting when the scheduler +dispatches it; it excludes time waiting in the queue. An unmeasured collection +has no invented zero. Opening details reads existing snapshots and declarations, +and does not demand collectors. `/doctor` summarizes bounded engine counters. +Routing, provider availability, conditions and priority explain presentation +policy. Enabled means the saved setting, not proof that the module is on screen. +The live condition check is shared with Native rendering; it does not infer +visibility from collection success. Width fitting can compact or omit lower +priority modules; the detail view does not claim to record the last frame's +exact compaction decisions. +`/shell` edits the current-shell module's visibility and side. + +## Context Packs + +A Context Pack is a JSON document (`nmsh.context-pack/v1`, at most 64 KiB) with an +id, version, license, provenance, compatibility (`contextApi`, NMSh version), the +capabilities it requires, module records and optional recommendations. Module +records use only literal labels, a fixed set of formatters (version, percent, +duration, until, clock, …), conditions over declared fields, core icon ids, +semantic roles, surfaces, show-on-command words and priority. + +**Packs are data, not plugins.** A pack cannot contain or reference shell text, +commands, argv, JavaScript, executable or file paths, templates, includes, hooks +or URLs. Parsing is strict and bounded: an unknown key is an error, so a future +field can never be silently accepted. A pack gains no authority by being +installed — it can only present facts that NMSh core already collects, under +their privacy policies. A pack requiring a capability this NMSh lacks is +**unsupported**, never partially active. + +```text +nmsh packs bundled and installed packs and their state +nmsh packs inspect FILE validate; show what it would read and add +nmsh packs install FILE [--sha256 HEX] [--replace] [--yes] +nmsh packs remove ID [--yes] +nmsh packs enable ID | disable ID +``` + +Installation shows what the pack reads and adds, then asks. With `--sha256` the +file must match that digest. Installs and removals are staged and atomic under +NMSh's config directory (`context-packs/`); a tampered or edited manifest is +reported as **integrity failed** and not loaded. Other states: enabled, disabled, +missing file, invalid, unsupported, incompatible. The `nmsh.*` namespace is +reserved for bundled packs. Installed modules start hidden. + +Recommendations are deterministic and explain their evidence ("go.mod in this +project", "kubectl is installed"). They never install a pack or enable a module. +Entering a repository never installs, enables or executes anything. + +There is no network registry or download: packs are local files you choose. + +## Agent context + +NMSh can show a Claude Code session's model, effort, context-window use and +rate limits through ordinary modules (`Claude Opus · Max · CTX 43%` and +`5H 61% · 7D 20%`). +It uses Claude Code's official status-line interface, never terminal scraping: + +1. `nmsh agent-status setup` shows the exact change to Claude Code's + `settings.json` (a `statusLine` command running `nmsh agent-status claude`) + and applies it only on Yes; `nmsh agent-status remove` removes exactly that. + An existing custom status line is reported, never replaced. +2. Claude Code runs the bridge with its status JSON. The bridge keeps an + allowlist (model, effort, context window and token counts, 5-hour, 7-day and + spend-limit use with their resets, duration, cost, lines changed, repository, + worktree and pull request, session name) in a private file (mode 0600) under + NMSh's runtime directory, tagged with the NMSh session it runs in; Claude's + own session id is kept only as a short digest, and transcript paths are + never stored. It prints a compact line for Claude Code using the same modules + and theme. +3. The `agent.claude` capability reads only the record for the current NMSh + session. Values are display-only (never kept in history); a report older + than two minutes says how old it is, and an absent one shows nothing. + Prompts, transcripts and terminal output are never read. + +The Context Engine does not depend on Claude Code; another agent with a stable +structured interface could supply the same fact shape later. + +## Discovery and security + +**Entering a repository never executes repository-controlled code through NMSh +context discovery, contacts the network, or exposes secrets.** Normal +user-configured shell startup/hooks and explicitly selected external prompt +providers are separate trust boundaries; context discovery does not inherit +their execution authority. + +Metadata reads refuse leaf symlinks and special files and are size-bounded. +Executables found on PATH are evidence, never permission: NMSh does not run +them for context, and refuses to even inspect one inside the current workspace, +writable by others or owned by another user. Only fixed system tools at absolute +paths are run (for memory and battery). The contextual Git collector uses trusted +Git, disables hooks and fsmonitor, does not inspect submodules, and fails status +closed for repository configuration that could run filters or fetch; missing +status is never shown as clean. + +Fact-derived display text is bounded and neutralizes terminal controls and bidi +formatting. It is never reused as a path or command. Pack metadata, workspace +names and settings text cross the same boundary in `/prompt`, `nmsh packs` and +`nmsh agent-status`. + +## Further reading + +- [Context Engine design](../design/context-engine.md): architecture, invariants and test gates. +- [Architecture overview](../../ARCHITECTURE.md): the frontend, shell transport, transcript and shared screen plan. +- [Native prompt customization](prompt-customization.md): style, theme, vibrance and Chroma. +- [Shell adapters](shell-adapter.md): the real zsh, Bash and Fish backends. +- [Privacy](../privacy.md): what NMSh stores and never records. +- [Security policy](../../SECURITY.md): NMSh's trust boundaries. diff --git a/website/src/content/reference/contributing.md b/website/src/content/reference/contributing.md new file mode 100644 index 00000000..431d1a74 --- /dev/null +++ b/website/src/content/reference/contributing.md @@ -0,0 +1,123 @@ +--- +title: "Contributing to NMSh" +description: "Canonical contributing to nmsh from the v0.20.0 published source." +group: Technical reference +order: 114 +source: "CONTRIBUTING.md" +--- + +Thank you for your interest in contributing to notMyShell (NMSh)! + +## Setup + +To get started with development, clone the repository and build the project: + +```bash +git clone https://github.com/raiseCatError/notMyShell.git +cd notMyShell +npm install +npm run build +``` + +## Tracking + +Before starting work, check: + +- **[ARCHITECTURE.md](ARCHITECTURE.md)** — how the pieces fit together; start here before changing a subsystem +- **[ROADMAP.md](ROADMAP.md)** — product direction and what is planned +- **[GitHub Issues](https://github.com/raiseCatError/notMyShell/issues)** — concrete actionable work; acceptance criteria in each issue are authoritative +- **[v0.20.0 Release](https://github.com/raiseCatError/notMyShell/releases/tag/v0.20.0)** — current stable release, merged through [PR #392](https://github.com/raiseCatError/notMyShell/pull/392) (see [ROADMAP.md](ROADMAP.md)) +- **[CHANGELOG.md](CHANGELOG.md)** — the authoritative record of notable changes; see [Changelog](#changelog) below +- **GitHub Project** — [NMSh Development](https://github.com/users/raiseCatError/projects/1) — live development status board + +## Branch Model + +Normal development and feature branches should target `dev`. `master` is reserved for stable/release-ready code. + +Typical flow: +```bash +git switch dev +git pull --ff-only +git switch -c feature/example +# Work on feature/example... +``` +Then submit a Pull Request from `feature/example` to `dev`. + +## Canonical verification + +During implementation, run focused affected tests. Use `npm run verify:fast` for +ordinary iteration (build, an explicit core test subset, and diff checks); it is +not the final gate. Before pushing a meaningful checkpoint, run `npm run verify` +(build, the full canonical suite, and diff checks). Build already checks the +source TypeScript; `npm run typecheck` remains available for direct use. + +For release-sensitive changes run `npm run verify:release`, which adds benchmark +script typechecking and bounded timing smoke. These commands reuse local +node_modules; use `npm ci` for clean CI/release environments. Batch coherent +changes and avoid pushing tiny or known-broken edits to use Actions as a test +runner. GitHub CI provides independent platform verification, not a replacement +for local checks. See [development verification](docs/development-verification.md) +for sharding, platform gates and exact-release evidence requirements. + +```bash +npm run verify:fast +npm run verify +npm run verify:release +``` + +*Note: When writing tests involving `TerminalApp`, you must carefully tear down child processes and temp ZDOTDIRs using `app['stop'](0)` and `app['session'].kill()` to prevent zombie processes.* + +## Guidance + +- **Read the docs**: Please read `README.md` first. +- **Understand the architecture**: Read `AGENTS.md` before making architectural changes. +- **Check the roadmap**: Review `ROADMAP.md` and the relevant [GitHub Issue](https://github.com/raiseCatError/notMyShell/issues) before starting large features. Acceptance criteria in the issue are authoritative. +- **Discuss first**: Open an issue to discuss large architectural changes before implementing them to ensure alignment. + +## Architectural Contribution Rules + +NMSh has several strict architectural invariants: + +- NMSh is a frontend over a persistent real shell. +- Do not replace the persistent PTY with command-by-command spawning. +- Raw PTY output must not be semantically recolored. +- NMSh-owned command presentation (like the input editor and submitted commands in history) may be styled. +- Fullscreen/interactive apps use the passthrough path. +- Preserve real shell state. +- Keep `SemanticService` detached from the controlling TTY. +- Test-created `TerminalApp` instances must cleanly tear down resources. + +For more details, see [AGENTS.md](AGENTS.md). + +## Pull Request Guidance + +When opening a Pull Request: +- **Keep it focused**: Address a single bug or feature. +- **Explain**: Describe what changed and why. +- **Test**: Include automated tests for behavior changes. +- **Validation**: Distinguish automated testing from physical terminal/manual verification (e.g., testing in Ghostty vs. macOS Terminal). +- **Scope**: Avoid unrelated formatting or refactors in the same PR. +- **Reproducibility**: Keep generated/demo assets reproducible using the scripts in `scripts/`. + +Commits do not need to follow an excessively strict convention (e.g. Conventional Commits), but clear, descriptive messages are preferred. + +## Changelog + +[CHANGELOG.md](CHANGELOG.md) is the single, authoritative changelog. It follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and is written for people using NMSh, not as a list of commits. + +- **Update it in the same PR as the change.** Add notable user-visible changes under `## [Unreleased]` at the top, in `### Added`, `### Changed`, `### Fixed`, `### Security`, `### Deprecated` or `### Removed`. Internal refactors, test-only changes and doc typos need no entry. +- **Say what changed for the user,** in one or two sentences, with the issue or PR link when it helps traceability. Describe only what is implemented and reachable; first slices, foundations and deferred work go under Known limitations or stay in the issue, not under Added. +- **Keep verification honest.** Do not describe automated tests or headless recordings as physical-terminal acceptance. + +When releasing: + +1. Rename `## [Unreleased]` to `## [X.Y.Z] - YYYY-MM-DD` using the publication date, add a one-paragraph overview, and add `### Compatibility`, `### Upgrade notes` and `### Known limitations` where they apply. Start a new empty `## [Unreleased]` above it. +2. Update the link references at the bottom of the file: `[Unreleased]` compares the new tag with `HEAD`, and `[X.Y.Z]` compares the previous tag with the new one. +3. Bump `package.json` and `package-lock.json` together (`npm version X.Y.Z --no-git-tag-version`), and point README, ROADMAP, SECURITY, AGENTS and `llms.txt` at the new release. +4. Write the GitHub Release notes from the new changelog section (a shorter summary is fine) and link to the full CHANGELOG.md. + +Past release sections are history: correct factual errors in them, but do not rewrite them otherwise. + +## Visual docs and demos + +README and [docs/demos.md](docs/demos.md) media come from committed VHS tapes in [`scripts/demos/`](scripts/demos/README.md). If a change alters what a clip shows, re-record it with `npm run demos` (or `npm run demos -- `) and commit the tape and the regenerated asset together. Recordings run against a disposable demo home and never read your own NMSh config, shell rc files or history. diff --git a/website/src/content/reference/copy-actions.md b/website/src/content/reference/copy-actions.md new file mode 100644 index 00000000..07283f24 --- /dev/null +++ b/website/src/content/reference/copy-actions.md @@ -0,0 +1,83 @@ +--- +title: "Output selection and Copy Actions" +description: "Canonical output selection and copy actions from the v0.20.0 published source." +group: Technical reference +order: 120 +source: "docs/development/copy-actions-slice-3.md" +--- + +This records the original isolated implementation. For the later 2C integration, see [the integration handoff](slice-3-integration.md). + +Branch: `feature/post-v020-copy-actions`. +Worktree: `/Volumes/nmsh-opus/wt/post-v020-copy-actions`. +Exact base: `069d954e21d4e35c69d2510f8173bddcd7c1b7c3` (verified before edits). +The worktree was created before the updated storage policy. It remains in place under that policy's existing-worktree exception. Subsequent test scratch and generated evidence use verified `/Volumes/T7Tmp/ai-dev` storage. No merge, rebase, cherry-pick, tag, release, protected-branch update or other active-worktree edit was performed. + +## Behavior and architecture + +`OutputSelection.ts` is transient identity/navigation state, not transcript storage or an action registry. Its candidates come only from `recentShellCommands()`. It records session/shell identity and the OutputBuffer presentation generation, which changes on clear/restore so reused numeric IDs cannot be targeted accidentally. The frontend resolves the live record again before activation. Disappearance cancels without moving an action to a neighboring record. + +New configurations use transcript output navigation for bare `/copy` and `/cp`. Explicitly persisted `quick` and `picker` values remain meaningful, including settings transfer. Explicit selectors, latest, status/report flags, auto-expand and `/copy ui` keep their existing paths. Enter uses `copyRecords()`; no payload or clipboard sanitization logic changed. A failed or empty copy leaves navigation available. Async clipboard completion only auto-expands records from the same presentation and live identity. + +`/actions` and `/action` use the same controller. Enter opens `openBlockPalette()` using `blockPaletteItems()` and the existing `BLOCK_ACTIONS`/`runBlockAction()` registry/dispatcher. The originating palette returns to selection on Esc; the second Esc restores the entry viewport. An explicit action ends navigation before edit/rerun/pager/report handoff. Numeric Actions forms open that existing palette directly. Live contextual availability is checked again at activation. + +Input ownership remains derived from the workspace/composer model. The selector excludes only itself and its own palette when checking eligibility. Running commands, startup, shell switch, hidden/key reads, passthrough, searches, workspace regions and foreign modal UI retain input. Normal typing exits navigation and continues editing. Passive pointer reports and page/wheel inspection keep selection available. + +Focus uses `selectedRowBand()`, including inverse video without color. Reveal operates over existing wrapped rows and leaves folded/filtered representations intact. Anchors distinguish historical headers, folded hints and filter hints from raw rows; synthetic gaps anchor to a neighboring stable row instead of the shared `lineIndex: -1`. The composer is never rewritten during navigation, preserving cursor, draft and paste atoms and avoiding stale restoration. Fold/filter state stays in its existing authority and is preserved by not mutating it. + +The independent multi-picker retains search, multi-selection, preview, ordering, status toggle, report and key operations. Checkmarks replace checkbox text, with independent focus bands and Safe equivalents. Compare reuses `framePanel()` rules and focus bands; paging/rendering reserve the same frame/header/footer geometry, with compact headers/controls on short screens. The diff engine, reports and clipboard diff are unchanged. + +## Verification and evidence + +Focused serial affected-area tests passed, including the existing copy/report/compare tests and the real-PTY zsh, Bash and Fish navigation fixtures. Source typechecking and diff checks passed. Normal color and Safe/NO_COLOR presentation are tested in separate process environments, matching production capability initialization. Tests clean up private shells/services and semantic resources. + +Canonical `verify`, `verify:release`, full-suite completion and performance benchmarks are **pending**, deliberately deferred because Gemini's canonical verification could still be using the shared host. No exact-feature CI was requested or claimed. Physical terminal validation remains pending. Linux Fish 3.7 issue #356 remains unresolved; passing local Fish fixtures do not settle it. + +Evidence directory: +`/Volumes/T7Tmp/ai-dev/runs/-u/post-v020-copy-actions-0f911fd7/20261016-221535-KGRW40/copy-actions-evidence`. + +Open `index.html` for production-renderer SVGs, or inspect `frames.json`, `.ansi` and `.txt` captures. Cases: Copy selection, Actions selection, contextual palette, palette return, `/copy ui`, Compare; 80×30 and 32×18; normal and Safe/NO_COLOR. `frames.json` records the source commit and tracked-dirty state. These are deterministic production frames using a simulated session boundary, not physical screenshots or real execution of displayed commands. Raw verification logs are `focused.log`, `pty.log`, `review-fixes.log`, `safe-no-color.log`, and `typecheck.log`. + +Reproduce focused tests (use explicit test paths, never bare npm test for this slice while verification is shared): + +```sh +rtk t7run --check +rtk t7run env -u NO_COLOR -u NMSH_COLOR TERM=xterm-256color COLORTERM=truecolor node scripts/dev/copy-actions-checks.mjs tests/outputSelection.test.ts tests/outputSelectionApp.test.ts tests/outputSelectionClipboard.test.ts tests/outputSelectionLive.test.ts tests/copyActionsPresentation.test.ts tests/copySelection.test.ts tests/compareOutput.test.ts +rtk t7run env NO_COLOR=1 NMSH_ICONS=safe node scripts/dev/copy-actions-checks.mjs tests/copyActionsPresentation.test.ts +rtk t7run npm run typecheck +rtk t7run node --import=tsx scripts/dev/copy-actions-frames.ts /absolute/T7/evidence/path +``` + +Dependencies were reused through read-only local package links; no package or lockfile changed. A fresh checkout can use the baseline's normal `npm ci` when resource coordination allows. + +## Failure history and review + +The first attempted focused `npm test` invocation unexpectedly expanded to the canonical suite. Only this agent's runner was interrupted; its queued-file cancellation errors are not suite validation. Later checks use `runTestFiles()` directly with explicit files. No other agent process was signaled. + +Initial red tests established missing navigation/defaults/aliases, clipboard generation reuse, vanished-target feedback, direct palette generation safety, synthetic-gap/fold anchors, passive mouse reports, and short Compare framing/chooser. Those tests remain and now pass. One test initially called a nonexistent `exportTranscript`/filter getter; those fixture API typos were corrected. The Compare fixture's final-side expectation was corrected to match existing A/B ordering; the diff algorithm was not changed. + +Existing `selectionRendering` assertions failed under the inherited unconfigured color environment, then passed under explicit xterm/truecolor with no assertion changes. Switching NO_COLOR inside an already imported colored process also exposed cached presentation constants in a test fixture; the Safe case now launches with NO_COLOR already established. The legacy copy-fold fixture now explicitly models a ready prompt when injecting completed records. Existing direct-copy live tests explicitly configure the preserved Quick Copy preference rather than depending on the changed new-config default. No failing assertion was weakened. + +One focused read-only reviewer found three important issues (gap anchors, passive pointer dismissal, and no diff on short screens). Each received a failing regression and fix. Follow-up inspection found no remaining Critical/Important concern; its minor short-chooser concern was also reproduced and fixed. + +## Integration sequence and overlaps + +No moving Slice 2 branch or uncommitted Gemini changes were read or incorporated. Reconciliation is intentionally deferred to the maintainer-approved integration window: + +1. Preserve this independent branch and finish/review Slice 2B, then Slice 2C on their own branches. +2. In a separate integration branch, apply Slice 3 after 2B/2C. Resolve `TerminalApp.ts` by responsibility rather than taking an entire side: imports/fields, slash dispatch, modal ownership, render decoration, Copy and palette handlers, notices, `/keys`, shutdown. +3. Reconcile `OutputBuffer.ts` clear/restore changes with summary metadata and folding. Keep `presentationGeneration` changes when IDs can be reused; do not bump it for ordinary fold/filter/reflow. Keep exact output/copy storage untouched. +4. Reconcile shared `configuration.ts`, `SettingsPanel.ts`, `slashCommands.ts` and generated help additions. Preserve both Queue settings/history additions and all saved Copy modes. `CopyPicker.ts` and `ComparePanel.ts` should remain focused presentation changes. +5. Verify typed Queue Summary metadata never enters `recentShellCommands()`, numeric copy numbering, Compare candidates or copied output. Summary rows must not inherit a command's `blockStartId` and receive its focus highlight accidentally. Test this with real 2B metadata after integration; this baseline has no typed saved-summary implementation. +6. Verify 2C's visible folded output still carries the completed record's stable `startId` through `blockStartId`. Extend the anchor representation discriminator if 2C adds a new hint kind. Confirm copying all 400 lines from folded queued output, search/filter restoration, and unchanged journal/raw exports. +7. Rerun affected tests and canonical/release verification on the integrated source once the host is free, then exact-feature CI. Obtain physical QA before release claims. + +Anticipated textual conflicts: `src/app/TerminalApp.ts`, `src/output/OutputBuffer.ts`, `src/prompt/configuration.ts`, `src/ui/SettingsPanel.ts`, `src/commands/slashCommands.ts`, `src/help/helpContent.ts`. Behavioral overlaps: summary row ownership, fold hints and viewport anchors, copied-output auto-expand, modal priority and shared notice geometry. New controller/tests are independently testable on the pinned baseline. + +## Physical QA still required + +Use Ghostty and Terminal.app at normal and narrow widths, including Safe/NO_COLOR, Bottom/Top/Flow and Normal/Chat. Try `/copy`, `/cp`, `/copy latest`, saved Quick Copy/picker preferences and numeric/range/list selectors; copy tabs, Unicode, trailing blank lines and `seq 1 400` into an external editor. Check silent output and clipboard-unavailable retry. + +Try `/actions`, `/action`, numeric forms, failure/reference filtering, Enter to palette, Esc to the same selection, Esc to the entry viewport/draft; confirm focus alone triggers nothing. Exercise edit/rerun/pager handoff and pointer Actions parity. Keep `/copy ui` search/multi-select/preview/report/status/order working. Inspect Compare at short heights, page to the last change, and copy the exact existing diff/report. Review fake-token/private-path redaction before report copy. + +During a running command, hidden/password read, single-key read and fullscreen application, confirm input stays with its owner and selection never opens. Check resize and wheel inspection, synthetic gaps/folded blocks and restoration from DETACHED/FOLLOW. No physical QA is claimed by this checkpoint. diff --git a/website/src/content/reference/github-workspace.md b/website/src/content/reference/github-workspace.md new file mode 100644 index 00000000..0c15b61a --- /dev/null +++ b/website/src/content/reference/github-workspace.md @@ -0,0 +1,223 @@ +--- +title: "Read-only GitHub workspace" +description: "Canonical read-only github workspace from the v0.20.0 published source." +group: Technical reference +order: 119 +source: "docs/architecture/github-workspace.md" +--- + +Status: **read-only**, included in v0.18.0 as `/github`, `/prs` and `/issues`. +There is no remote mutation of any kind; the merge preview only describes what a +merge would do. Physical terminal QA is **still pending**. + +Boundary: GitHub owns remote PR and issue truth. Git owns local checkouts (see #337). +NMSh owns presentation, navigation and safe interaction. This is a terminal control +surface, not an IDE or a GitHub replacement. + +## Try it + +From the isolated worktree: + +```sh +cd /private/tmp/notMyShell-github-workspace +npx tsx scripts/dev/github-workspace-preview.ts +``` + +| Mode | Command | +| --- | --- | +| Fixtures (offline, default) | `npx tsx scripts/dev/github-workspace-preview.ts` | +| Fixture error scenario | `… --scenario=auth` (`ok empty auth rate_limit offline slow partial flaky`) | +| Live, read-only | `… --live --repo=raiseCatError/notMyShell` (uses your authenticated `gh`) | +| Printed frames (no TTY) | `… --static --width=40 --height=20 --keys=down,enter,tab,m` | +| Safe glyphs / no color | `… --safe`, `… --no-color` (`NO_COLOR=1` is honored) | + +Keys that only exist in the preview: + +| Key | Action | +| --- | --- | +| Ctrl-E | Next fixture scenario | +| Ctrl-G | Switch between Safe and Nerd glyphs | +| Ctrl-K | Color on/off | +| Ctrl-W | Cycle the simulated width: 30/40/50/80/120/200/terminal | +| Ctrl-C or Q | Quit; the terminal is restored | + +The preview uses the alternate screen and restores the terminal on exit, on a signal +and on an uncaught error. It is not imported by `src/` and does not change NMSh startup. + +## Modules (`src/githubWorkspace/`) + +| File | Role | +| --- | --- | +| `model.ts` | Typed facts and `LIMITS`. Fields GitHub did not report stay `undefined` or `'unknown'`. | +| `sanitize.ts` | Untrusted text goes through here: control and escape sequences and bidi overrides are stripped, and length is bounded. Also holds the owner/repo allowlist and `https://github.com`-only URLs. | +| `query.ts` | GitHub search query passthrough, view scoping, honest notices, bounded history. | +| `transport.ts` | Read-only `gh api` transport: argv only, bounded by time and bytes, credential-free errors. | +| `queries.ts` | Bounded GraphQL documents that request `totalCount`, so truncation is reported. | +| `source.ts` | `GithubSource` interface, GraphQL/REST to model mapping, `GhSource`. | +| `service.ts` | Bounded LRU caches, request de-duplication, 2-request concurrency limit. Patches are keyed by head SHA. | +| `diff.ts` | Bounded read-only diff model, compact summary, external-review intent. | +| `mergePlan.ts` | Merge plan contract, the always-refusing `executeRemoteWrite`, and the `OpenRelatedWorktree` intent. | +| `controller.ts` | Keyboard-first state machine with one focus owner. Fetches only on explicit actions. | +| `render.ts` | Pure renderer: state to exactly `rows` lines of at most `columns` cells. No I/O. | +| `fixtures.ts` | Deterministic raw GitHub-shaped data, including hostile entries. It runs through the real mappers. | + +## Data sources (factual) + +| Data | Source | +| --- | --- | +| List and search | GraphQL `search(type: ISSUE)`, 25 results per page, cursor pagination, `issueCount`, `rateLimit`. Summary fields only: state, draft, head/base, review decision, `mergeable`, head commit `statusCheckRollup`, additions/deletions/files. | +| PR detail | GraphQL `repository.pullRequest`, fetched lazily when you press Enter on an item. It includes `headRefOid`, `mergeStateStatus`, individual CheckRuns and StatusContexts (with `isRequired`), the last 100 commits, issue comments, reviews and inline review comments (kept distinct), the first 100 files, and repository merge policy (`mergeCommitAllowed`, `squashMergeAllowed`, `rebaseMergeAllowed`, `deleteBranchOnMerge`, `viewerPermission`). | +| Patches | REST `GET repos/{o}/{r}/pulls/{n}/files`, fetched lazily the first time the Diff tab opens. Up to 3 pages of 100, cached per head SHA. | +| Issue detail | GraphQL `repository.issue`: labels, assignees, comments. Related links come only from `closedByPullRequestsReferences` and `CROSS_REFERENCED_EVENT`. Nothing is inferred from titles or branch names. | + +Partial GraphQL errors keep the data that did arrive and show it labelled "Partial +results" or "Partial data". Collections GitHub reports as larger than what NMSh holds +are labelled "Showing N of M". + +## Authentication boundary + +- Authentication belongs to `gh` (keyring or config). NMSh never reads, prints, stores + or passes a token, and never puts credentials in argv. +- `execFile('gh', argv)` runs without a shell. The query, owner, repo and number are + GraphQL variables: strings go through `-f` (a raw field, never expanded as `@file`), + and validated integers go through `-F`. The working directory is `$HOME`, so the + repository you are in has no effect. +- Each call has a 20 s timeout and an 8 MiB output limit. The environment sets + `GH_PROMPT_DISABLED`, `NO_COLOR` and `GH_PAGER=cat`, and removes `GH_DEBUG` and `DEBUG`. +- Error text has token-shaped strings redacted and is sanitized. Failures are + classified as `auth`, `rate_limit`, `network`, `not_found`, `invalid_query`, + `timeout`, `too_large`, `unavailable` (gh missing) or `unknown`. All are recoverable + with R. Results already on screen stay visible and are labelled with their age. +- Network access only happens on explicit actions: submitting a query, Tab to a view + that has never loaded, N for more results, Enter on an item, opening the Diff tab, or R. + +## Read-only policy + +`GhCliTransport` has exactly two methods: `graphql` and `restGet`. `graphql` rejects +any document that is not a single `query`. `restGet` always sends `--method GET` and +accepts only `repos///…` paths built from validated parts. +`executeRemoteWrite` (merge, deleteBranch, submitReview, closeIssue) always throws +`RemoteWriteDisabledError`. Tests assert all of this. No background agent may merge. + +## Query semantics + +The text you type is kept as the **original** query. The **effective** query is +exactly what GitHub receives, and it is displayed. GitHub's search remains the +authority on what a query means. + +- **PRs view:** adds `is:pr` when the query does not specify a type. A query that + asks only for issues is refused with an explanation. +- **Issues view:** the same, with `is:issue`. +- **Search view:** the query is passed through unchanged and can return both PRs and issues. +- Local checks reject: empty queries, more than 256 characters, control characters, + an unbalanced `"`, and more than 5 AND/OR/NOT operators (a GitHub limit). +- An undocumented `qualifier:` produces a warning that GitHub may treat it as text. + It is never silently dropped. +- Queries run only when submitted with Enter, never per keystroke. Search history + keeps at most 20 entries. +- Results are capped at 8 pages and 200 items. GitHub search itself navigates at most 1000. +- Selection identity is `kind:owner/repo#number`. A refresh keeps the selected item + when it is still present, and says so when it is gone. + +## Keyboard UX + +Exactly one region owns the keyboard: `list`, `query`, `detail` or `merge`. The +header names it (`[focus: …]`). The active tab is shown in brackets, and the selected +row has a marker (`>` or `›`) and is also shown inverse when color is on. None of this +depends on color. + +| Context | Keys | +| --- | --- | +| List | Up/Down (j/k) to select, PgUp/PgDn, Home/End, Enter to inspect, `/` to search, Tab/Shift-Tab to switch PRs/Issues/Search, R to refresh, N for more, O to open on GitHub (intent), Q/Esc to quit | +| Query | Type, Backspace, Enter to submit, Esc to cancel, Up/Down for history | +| Detail | Tab/Shift-Tab between sections, Up/Down/PgUp/PgDn to scroll, N/P for next/previous file, Enter on Files to open its diff, D for compact/review depth, R to refresh, M for merge preview, W for worktree intent, O to open, Esc to go back | +| Merge preview | Tab for method, B to toggle branch deletion, Up/Down to scroll, R to refresh and replan, Esc to close. Enter only explains that merging is disabled. | + +The footer shows only the actions available right now. At narrow widths it falls back +to keys only (`Esc back Tab ^v Pg R M W O`). + +## Diff behavior and limitations + +The diff has three depths: + +- **Compact:** files changed, +/- totals and the largest files. +- **Review:** one file at a time, with hunks, old/new line numbers and context lines. N/P move between files. +- **External:** an `OpenExternalReview` intent pointing at the PR's `/files` URL on GitHub. + +Every diff view is headed with its source: "Remote GitHub PR diff", the repository and +PR number, base ← head, and the head SHA. Each file is labelled with how complete its patch is: + +| Label | Meaning | +| --- | --- | +| `complete` | Patch present, and its +/- counts match what GitHub reported. | +| `incomplete` | Patch shorter than GitHub's counts; GitHub abbreviated it. | +| `truncated` | NMSh's 1,500-lines-per-file bound cut the patch. | +| `omitted` | GitHub sent no patch (binary or too large). A rename without content changes is labelled as such. | +| `skipped` | NMSh's 20,000-line total budget was already used. | + +When GitHub reports more files than were loaded (the 300-file bound), the diff says +it is NOT complete. Patch lines are sanitized, tabs are expanded, and each line is +limited to 1,000 characters. Nothing in a patch is evaluated. In color mode, long +lines are truncated rather than wrapped. There is no syntax highlighting and no +side-by-side view; those are for #332. + +## Unknown and stale states + +- Mergeability: GitHub `UNKNOWN` displays as "unknown (GitHub has not computed it)". +- Merge state: "unknown (not reported)" when GitHub did not report it. +- Checks: the rollup, plus each individual check with its status and conclusion. + Required is shown as `(required)`, `(required: unknown)`, or not shown when GitHub says it is not required. +- Review decision: a `null` decision is shown as "no review decision reported". +- Data older than 2 minutes is labelled STALE with its age. A failed refresh keeps + the earlier data with an explicit "Refresh failed; showing … from Xm ago". + +## Secure future merge transaction (not implemented) + +`planMerge(detail, {method, deleteBranch})` builds the preview: repository, PR number, +**exact head SHA**, head → base, per-method support (`allowed`, +`disabled_by_repository` or `unknown`), mergeability, merge state, and conditions +(blockers, unknowns, warnings: draft, conflicts, blocked or behind, requested +changes, required checks that failed or are pending, permission, stale data, +truncated checks). It also carries a warning that this is a remote, shared action, +and a separate destructive warning when branch deletion is requested (noting a +cross-repository head when there is one). `executable` is always `false`. + +A future integration must: + +1. Re-fetch the PR from GitHub immediately before acting and rebuild the plan. +2. Show the plan and require explicit confirmation from a human. +3. Call the merge with `expectedHeadOid` (GraphQL) or `sha` (REST) set to the + previewed head SHA, so a push in between makes the merge fail. +4. Delete the branch as a separate, separately confirmed step, and report partial + success (merged, branch kept) truthfully. +5. Never bypass branch protection, and never infer readiness from local Git. + +## Live routing (v0.18.0) + +`/github` and `/prs` open the PRs tab, `/issues` the Issues tab, for the +repository of the shell's `origin` remote when it is on github.com (https, ssh or +scp form; anything else opens nothing and says why). TerminalApp constructs +`GhSource`, `GithubWorkspaceService` and `GithubWorkspaceController` and renders +in the shared panel slot; NMSh's decoded keys map onto `WorkspaceKey` +(`src/githubWorkspace/host.ts`), with Tab and Shift+Tab switching tabs. +Intents: `OpenExternal` and `OpenExternalReview` open only `safeGithubUrl` URLs +with the system opener; `OpenRelatedWorktree` opens `/worktrees` on the PR's head +branch (same-repository PRs only); `Exit` closes the panel. + +## Future integrations + +- **#336 (semantic actions):** `availableActions()` becomes the action list. Mouse + actions call the same handlers. +- **#331 (sidebar):** reuse `SearchItem`, `itemKey` and `checksLabel` for quick links. + Deep management stays here. +- **#332 (detail and diff panel):** consume `DiffModel` and `detailLines`. The right + panel replaces the inline review depth. + +## Verification + +Automated: `tests/githubWorkspace.test.ts` covers queries, identity, pagination, +refresh, stale and error states, transport safety, hostile content, layout at +widths from 30 to 200, Safe and NO_COLOR rendering, the diff, checks, merge +planning, disabled writes, the keyboard, intents, bounds, and render purity. + +**Human physical QA in real terminals is pending.** diff --git a/website/src/content/reference/onboarding.md b/website/src/content/reference/onboarding.md new file mode 100644 index 00000000..3286d527 --- /dev/null +++ b/website/src/content/reference/onboarding.md @@ -0,0 +1,345 @@ +--- +title: "First-run behavior and validation" +description: "Canonical first-run behavior and validation from the v0.20.0 published source." +group: Technical reference +order: 121 +source: "docs/testing/post-v020-preferences-onboarding.md" +--- + +Source: `825336393562a37cb5cdf9baf916e8d6d3bb4e6d` on +`stabilize/post-v020-slice3`. Implementation branch: +`feature/post-v020-preferences-onboarding`. No package version change or merge. +Specification: [accepted scope](../design/post-v020-preferences-onboarding.md). + +## Behavior and compatibility + +Recommended is the canonical fresh configuration: NMSh / Lavender Native, +standard vibrance, theme UI text On, text tint Off, Lavender surface, theme +chrome and Chroma Off. Rich motion uses Expressive context/events, Sweep launch, +Vivid completion highlight, Seal completion, cursor travel On, Medium/Normal +independent tuning; effects, shimmer and milestones are On. Reduced Motion is +Off by default; environment/user accessibility and color gates still apply. + +Transcript: Chat, Full snapshots, Grayscale history, divider On / Normal / +Follow history, Smart folding. Typed queue summary saving is On, Detailed / +center / left, durations and exit codes On, waits Off. Queue output remains +Inline and failure policy Pause. Title uses Project + Session. Cursor is Auto / +Fire / Follow theme; shape and blink remain host owned, idle effect Off. + +Missing configuration receives Recommended. Saved objects with omitted fields +retain historical Clean/no added motion, no cursor effect, neutral surface, +Normal transcript, Follow prompt history/Chroma divider, summaries Off and title +Off. Explicit fields and unknown declarative fields survive the ordinary atomic +save path. Theme surface tint remains specific to Lavender Native. Portable +exports include motion/cursor settings and exclude installation progress. + +Minimal uses Native Minimal, Flow, Normal, historical prompt display Off, +Chroma/effects Off, no welcome or greeting, restrained spacing; One-line is +`composerLayout=oneLine`, Two-line is `twoLine`, both with composer placement. +Shell, queue, clipboard and safety behavior are preserved. An archived greeting +is hidden under Minimal without deleting it from the saved transcript. + +Fresh onboarding opens the Reactive Horizon starting point (specified in +[first-run startup](../design/first-run-startup.md); the static centered modal +it replaces is archived): four choices under the real Aurora engine, whose mood +follows the highlight with a 700 ms cross-fade, Vespyr composited pixel by pixel +on the horizon, review before Apply and *Leave setup?* with Stay first. +`onboardingBackdrop` (aurora/subtle/off) is saved at once with `A`. `introSeen` +is separate from completion: fresh False, legacy missing True, persisted before +the selector displays, so leaving does not replay it next launch. Attach and +explicit setup routes bypass installation onboarding. A failed save keeps the +draft and displays the error. Customize Everything opens the guided setup (in +the selector's visual language) over the saved configuration; leaving it without +changes returns to the choices. Keep Existing reads the environment read-only. +Apply folds the sky into the real transcript and composer; a genuine first run +adds Vespyr's block and the first-run welcome once, whichever starting point it +chose (Minimal keeps no welcome on later launches); every later Apply adds one +receipt line. `/startup`, `/setup quick` and Settings → Starting point reopen the +selector; `/setup` opens the guided setup directly. Custom fire gradients and +explicit Reduced Motion survive. The one-time window offer and its checklists: +[first-run window maximization](first-run-window-maximization.md). + +Setup Cat keeps one fixed frame: the header and step navigation stay put, rows +start on the same line on every step and scroll with the selection visible, the +keys sit on the last row, and the composer position never moves it. Editors it +cannot embed open over it; Esc comes back to the same step, row and unapplied +draft. Color editors opened from Setup write into its draft, nothing is saved +until Apply. + +Tools: one panel and installer everywhere (`/tools`, Settings → Tools, the +guided setup, and the review's Tools line in `/startup`, changed with T). +Recommended is ripgrep, fd, fzf, jq, bat, zoxide and tealdeer; Fastfetch stays a +supported welcome provider without a tier. V cycles the view (Recommended → +Recommended + Enhanced → All curated tools; Recommended + Enhanced by default) +and never changes the selection. Keys act only outside search (`/` focuses it): +Space selects one, R adds the missing Recommended tools, E the missing +Recommended + Enhanced ones, C clears; Enter reviews the exact commands of every +selected tool, then one confirmation (No first; green Yes, install; red Yes, +uninstall) → per-tool results. `/startup` Recommended defaults to Review and +install Recommended + Enhanced: the view's verified missing tools are ticked +after the ending; Minimal and Keep Existing default to Skip. Status words carry +selection (✓ Selected); the band is the cursor only. A check that fails or does +not finish is *Unable to verify*, never Missing, and is never selected or +installed. Installing zoxide, fzf or Atuin does not turn on their shell hooks. + +Custom colors: choosing Custom opens its editor at once (Setup and Settings). +The shared gradient editor offers Lavender, Purple, Blue, Cyan, Teal, Green, +Amber, Orange, Rose and White with P, a live preview bar, each stop's hex +(Enter edits it), S to apply and Esc to cancel. The cursor idle effect has its +own color: Follow cursor effect (the default, exactly as before), Follow theme, +a preset or Custom. + +Updates: with Notify, a newer stable release shows one amber notice in the +transcript, once per version: both versions, two lines of notes, and the way to +update this installation (brew commands for Homebrew, `/update` for a verified +checkout, the release page otherwise; no install scripts). Automatic shows the +same notice when it cannot update this installation itself. Off checks nothing. + +Fire presets are gradients over existing particles, not a new engine. Purple, +Blue, Green and Classic have hot-to-cool stops. Follow theme derives several +stops from the resolved cursor theme. Custom opens the existing gradient editor. +Ghostty native uses generated palette endpoints after managed shader setup; +Auto falls back to Portable on Terminal.app and unconfigured/unsupported hosts. +Forced native reports its limits. NO_COLOR, effects Off and Reduced Motion gate +effects; Safe glyphs use existing portable glyph capabilities. Physical caret +color never changes. Native shaders remain subject to explicit host setup/reload. + +## Folding investigation — physical Test B + +The reported failure was a queued `seq 1 450` with Fold requested. The original +session's effective value was not recorded. It remains **unreproduced**; no +production folding policy or threshold was changed. + +Real zsh, Bash and Fish tests traverse `/settings queue` and `/queue settings`, +verify the immediate saved and live outputDisplay value, queue through the real +composer behind `sleep`, and inspect `OutputBuffer.complete()` arguments. +Observed Fold: Smart, queued=true, foldQueued=true, holdOpen=false, +interrupted=false, FOLLOW/no selection → collapsed, 442 hidden, all output intact. +Inline intentionally passes foldQueued=false and stays expanded. Detached or +selected inspection and queued failures/interruption stay visible. Foreground +Smart still folds normally; Never remains Never. Boundary/large-output tests +cover copy/newlines, tabs/Unicode, resize, explicit expansion and restore. + +The headless real-PTY fixture answers the pre-ready device-attributes query Fish +4 sends at startup. Without a reply, local Fish 4.9.3 waits 10 seconds, warns +and continues — the tests' own 10-second budgets cannot afford that, and this +was a fixture failure, not a shell or folding behavior. The session answers the +editor's queries only after it has read the readiness marker, so the fixture +also answers a query that arrives in the same read as that marker (captured as +startup output and otherwise owed an answer); a plain pre-readiness guard left +exactly that read unanswered, which was the residual focused-suite flake. No +production folding behavior was altered to bypass it. + +## Focused-suite fixture determinism + +The real-PTY folding suites assert disclosure decisions, queue provenance and +exact copies. Three fixture mechanics were made deterministic without changing +production policy: + +- The reader-inspection guard detaches by scrolling to the very top of history. + A near-bottom offset could be undone by any later render: + `HistoryViewport.resolve()` re-attaches FOLLOW once the clamped offset reaches + the viewport maximum, so a small PTY tail chunk delivered while the command + still ran flipped the viewport back to FOLLOW and the completion decision + correctly stopped holding the block open. At the top, the re-attach condition + (`maximum = 0`) cannot be reached while the run holds more rows than the + viewport height (the guard only scrolls past 30 rows). +- The queued composer lifecycle keeps its foreground (`sleep 1.5`) alive past + the ownership look. The composer Enter is held while NMSh probes which + program owns the terminal; the look gets an 800 ms budget + (`OWNERSHIP_LOOK_MS`) before the held Enter is released. If that release + landed only after the foreground ended, the replay routed as an idle + submission instead of a queue entry; 1.5 s leaves roughly 700 ms of margin + under load. +- Enqueue observability waits for both observables — the synchronous editor + clear and the asynchronous `queueState` event — because the two can separate + by a task tick under load. The wait stays bounded by the test's deadline. + +`copyExactLive` teardown can time out its sandbox ownership scan (`lsof`, 2 s +per scan) on the loaded external volume. A timed-out scan is treated as "owners +not established"; disposal keeps polling inside its existing budget, and root +removal retries (`maxRetries: 5`). This is an environment-latency class, not a +product failure. A bash job-control race can also inject +`child setpgid (…): Operation not permitted` into command output under 4-way +load, breaking exact-byte copy comparisons in bash variants; it is classified +environmental and deliberately not filtered, because filtering it would weaken +the exact-output assertions. + +## Composer history while commands run + +Physical QA on this branch found that ↑/↓ stopped recalling command history while +any command executed. The recall path had used the run state itself as the gate, +but execution is not input ownership: a command that reads nothing leaves the +composer — and its history — untouched. Recall is now decided by one predicate, +`composerHistoryEligible`: an explicit queued-entry edit, a passthrough program or +an external passthrough keeps the keys entirely; explicit Ctrl+Q queue-compose and +an idle composer always recall; a running command recalls exactly when no direct +reader (hidden line or single key) and no line-waiting state owns input. The +direct-reader and line-waiting checks are defense in depth — those keys are routed +to the program earlier in key handling. A shell switch (`/shell bash`) resets the +navigation so an arrow afterwards steps the new shell's history, never the old +shell's snapshot; the editor draft is untouched. Anything recalled reaches the +queue only through an explicit Enter. Evidence: 18 app-level key-routing cases +(`composerHistoryRunning`) plus real zsh/Bash/Fish PTY regressions in +`commandQueueLive`. + +The live sandbox now isolates `XDG_DATA_HOME` like `HOME`. An ambient value (this +machine's environment sets one) pointed every Fish sandbox at one shared +`fish_history`; imported entries from earlier runs made the new Fish regression +order-dependent — a stale `echo QUEUED-NEXT` entry satisfied the "recorded by the +provider" filter for a freshly enqueued submission, so the first ↑ recalled the +queue entry instead of the executed command. With the sandbox value pinned to its +own home, the import is hermetic. + +## Selected surface root cause + +The old shared band only reopened its background after exact SGR 0 resets. +Historical prompt segments explicitly set backgrounds and SGR 49; Powerline +connector foregrounds retained the old segment color. Those cells punched +through the band. Selection now uses the existing cell overlay after each cell's +own styles, blends connector foregrounds into the selected surface, preserves +readable semantic text and neutralizes inverse video during selected painting. +Unselected text, glyphs, widths, data, copy payloads and hit testing stay intact. +The cell evidence helper accounts for inverse video's displayed color swap. + +## Automated evidence and independent review + +Focused affected suites passed at concurrency 4 on the fixed tree: 182/182 +including the real-PTY folding file (`runs/bash/notMyShell-bbb70309/20261017-164655-Jl0NjO` +with `NMSH_TEST_TMPDIR` on T7). The folding file then held across 13 consecutive +runs under the original 4-way load class — one quiet run (9/9) plus three waves +of four concurrent runs each (12/12) — `20261017-164758-8oAyYY`. `verify:fast` +passed before full verification. Canonical full release verification passed on +the final tree: build, 2877 tests / 2876 pass / 0 fail (1 skipped), +`git diff --check`, benchmark typecheck and timing smoke, exit 0 — +`20261017-170015-jS37Hi`. A first full run in the same environment produced the +same suite with one environmental disposal failure (`20261017-164959-dfGJNm`): +an unrelated Finder process briefly matched a live sandbox's process-ownership +scan, a transient false positive with no handle left behind. The rerun was fully +green and no production behavior was changed for it. Exact-commit CI is recorded +in the final handoff and external evidence manifest; do not infer physical +acceptance from CI. + +One independent read-only review found seven issues, each reproduced RED before +its fix: below-minimum preview cleanup, explicit welcome restoration, inverse +selection colors, Custom flame labels, attach onboarding, hidden Manual save +errors and 16×6 controls. Their regression suite passed GREEN after the bounded +fix pass. Capture inspection also caught Recommended preserving an old Never +fold preference; explicit Recommended now selects Smart, with a lifecycle demo +regression. No review finding was intentionally deferred. + +The static-onboarding correction was verified on its own tree: affected setup +suites 68/68 (`postV020Onboarding`, `postV020Preferences`, `setupQuickPreview`, +`setupCat`) and the wider eleven-file onboarding/chroma set 147/147, then +canonical full release verification passed — build, 2912 tests / 2911 pass / +0 fail (1 skipped), `git diff --check`, benchmark typecheck and timing smoke, +exit 0 — `20261017-194626-Mr0fg4`. Renderer-level captures for 16×6 TINY +through 200×50 (mutation states plus backdrop off/subtle at 80×24) are in the +external QA `onboarding-captures/` directory beside `run-qa.sh`: all frames +are byte-identical across renders and every capture stays inside its bounds. +The R-replay/`renderSetupIntro` engine remains tested but unreachable from the +quick path. Physical Ghostty/Terminal.app validation of the modal is pending. + +## Reproduce without touching real preferences + +The reusable external runner defaults to focused tests; expensive release +verification is opt-in, never a pre-commit hook: + +```sh +t7run --check +t7run bash /Volumes/T7Tmp/ai-dev/qa/post-v020-preferences-onboarding/run-qa.sh +# Explicit larger gate: +t7run bash /Volumes/T7Tmp/ai-dev/qa/post-v020-preferences-onboarding/run-qa.sh \ + /Volumes/T7Tmp/ai-dev/worktrees/notMyShell/post-v020-preferences-onboarding release +# Production ANSI/cell/SVG/gallery evidence, no shell processes: +t7run bash /Volumes/T7Tmp/ai-dev/qa/post-v020-preferences-onboarding/run-qa.sh \ + /Volumes/T7Tmp/ai-dev/worktrees/notMyShell/post-v020-preferences-onboarding captures +``` + +Logs/captures print their own external T7 directory. The gallery's R Replay +replays recorded states of the retained selector engine; the quick setup modal +itself is static and has no replay affordance. +These captures use TerminalApp and production prompt, presenter, geometry, +motion and cursor components; the SVG viewer serializes their terminal cells. +They do not establish physical font/shader/keyboard or mouse correctness. + +## Physical acceptance — Ghostty and Terminal.app + +Use a disposable home/session, keeping all artifacts on the T7 drive. The +frontend runs from `dist/`, so build the branch first (`npm run build`; the +release verification does this automatically). + +```sh +cd /Volumes/T7Tmp/ai-dev/worktrees/notMyShell/post-v020-preferences-onboarding +t7run bash -c 'fixture=$(mktemp -d "$T7_STORAGE_ROOT/t/prefs-human-XXXXXX"); \ + mkdir -p "$fixture/home" "$fixture/config" "$fixture/runtime"; \ + chmod 700 "$fixture/runtime"; \ + HOME="$fixture/home" XDG_CONFIG_HOME="$fixture/config" \ + NMSH_RUNTIME_DIR="$fixture/runtime" NMSH_DISABLE_UPDATES=1 NMSH_SESSION_SERVICE=0 \ + node dist/index.js' +``` + +1. Fresh: the starting point appears with the aurora moving above the horizon + and Vespyr standing on it (no rectangle behind the cat). ↑↓ and 1–4 move + the highlight; the sky's mood follows it (aurora, calm, spectrum, out) in a + short cross-fade with no flash, however fast you press. A cycles Aurora on, + Subtle and Off, and a restart keeps it; after a minute without keys the sky + holds still and the next key resumes it. Esc opens *Leave setup?* with Stay + focused; Leave applies nothing and says so in one line. Resize through 125, + 96, 61 and 40 columns and down to 16×6: two columns, the accordion, the plain + list, with every choice, Apply and Leave reachable. With Reduced Motion, + Effects Off, NO_COLOR and 16 colors the sky is still or absent. +2. Recommended, Enter, Enter: the sky folds up into one fading rule, the real + composer rises into place (the caret appears once it lands), and the + first-run welcome arrives line by line under Vespyr's block. A key during + the ending finishes it and types once. Restart with the same fixture: no + selector, no second welcome. Minimal: Two-line by default, ←→ One-line, one + receipt line, Flow composer, no greeting. Keep Existing on a machine with + Starship started by `~/.zshrc`: it is kept, the receipt names it, and no file + outside NMSh's configuration changes (compare timestamps before and after). +3. `/startup`, `/setup quick` and Settings → Starting point reopen the selector + at the current starting point; applying it again adds only a receipt. + Customize Everything opens the guided setup in the selector's language + (route of steps, *Step N of M*, the selected row as a segment, keycaps; the + live preview beside the rows from 96 columns); Esc there returns to the + choices without closing setup. `/setup` opens the guided setup directly; + `/setup prompt`, `/setup look`, `/setup motion` and `/setup cursor` remain + direct. Manual errors stay visible and keep the draft. On Terminal.app and + kitty the first run may also offer to maximize the window: follow + [its checklist](first-run-window-maximization.md). +4. Recommended: inspect requested defaults in `/settings`, `/theme`, `/motion`, + `/transcript`, `/cursor` and `/queue settings`. Try colored flames and Custom; + change theme, preserve custom stops. Ghostty native needs reviewed setup and + host reload. Terminal.app uses Portable. Never expect hardware caret recolor. +5. Folding Test B: `/queue settings`; explicitly observe **Queued output display: + Fold queued output**, then Esc (saves live). Run `sleep 5`; while it runs type + `seq 1 450`, Enter. Stay at latest and avoid selecting. Expect head 1/2/3, + **442 lines hidden**, tail 446–450. Ctrl+O expands; Ctrl+O collapses. `/copy` + and `/cp` copy the complete output; `/copy ui` can preview/select/report it. + Repeat while detached or drag-selecting: expanded retention is intentional. + Repeat Inline and Never separately, including nonzero/interrupted queued work. +6. Select completed output by `/copy`, keyboard arrows and pointer. Inspect + Lavender Native and another theme, Normal/Chat, folded/expanded, 32/50/80+ + columns, Nerd/Safe, truecolor/ANSI-16/NO_COLOR. Expect a continuous selected + surface including wedges and inverse output, readable colors and unchanged + click targets. Esc restores the draft/view; copy never acquires ANSI chrome. +7. If Test B fails, record host/shell versions, active config path, effective + Queued output row, FOLLOW/DETACHED, active selection and exact key sequence. + Do not assume the on-disk preference was active in the original session. +8. Composer history while a command runs (the queued-execution regression). + Run `echo RECALL-ONE` then `echo RECALL-TWO`. Start `sleep 30`; while it + runs, type `echo QUEUED-NEXT` and Enter — it queues (the queue row counts + it). Type `echo DRAFT-KEEP`, then ↑: the composer shows `echo RECALL-TWO`, + ↑ again `echo RECALL-ONE`; ↓ walks back and finally restores + `echo DRAFT-KEEP` exactly at its original caret. Navigation alone never + runs or queues anything. Ctrl+U then ↑ recalls freshly; Enter queues it. + `/queue` still owns ↑/↓ for its list, and editing a queued entry keeps + ↑/↓ in that entry's draft. With a program waiting for a line (`read`, + `sudo`) or a full-screen app (`vim`, `less`), arrows belong to the program; + Ctrl+Q while it waits composes a queue entry whose ↑/↓ recall history. + `/shell bash` and then ↑ starts navigation fresh in the new shell while the + typed draft survives. + +Linux Fish 3.7 input safety and physical GUI/shader interaction are separate +acceptance evidence. Local real-PTY tests use installed macOS shell versions; +report remote CI package versions separately when available. diff --git a/website/src/content/reference/optional-tools.md b/website/src/content/reference/optional-tools.md new file mode 100644 index 00000000..36e8989d --- /dev/null +++ b/website/src/content/reference/optional-tools.md @@ -0,0 +1,57 @@ +--- +title: "Optional tools and providers" +description: "Canonical optional tools and providers from the v0.20.0 published source." +group: Technical reference +order: 105 +source: "docs/architecture/optional-tools.md" +--- + +`/tools`, Settings → Tools and the command palette open one terminal-native +browser. Discover searches the offline curated catalog by name, description or +category. Within categories missing tools precede installed tools. Installed +contains detected executables; Configure contains only real supported adapters; +Errors contains retained installation failures or missing selected providers. +No marketplace, update feed, network discovery or required external tool exists. + +Up/Down selects, Left/Right changes tabs, typing searches, Enter opens details, +Esc returns/clears/closes. Details show executable/version evidence, category, +source and install package. `I` previews installation when missing, `C` opens a +supported configuration adapter, `P` opens the existing provider chooser, and +`R` explicitly refreshes detection. Toolchain detail labels consume offline +Linguist through Chroma identity colors; status text remains separate. + +Detection uses the existing cached provider resolver and bounded version probes, +in batches of three on opening/refresh. Rendering never launches a probe or +fetches network data. Installed means an executable is present, not that a daemon, +credential, zsh hook or environment is healthy. Configured means an installed +tool is selected in NMSh settings, not that its shell hooks are active. Hook +inspection/setup is deferred and never inferred. Direct shell tools work normally. + +Recommended tools: ripgrep, fd, fzf, jq, bat, zoxide and tealdeer. Enhanced +tools are optional improvements; other entries (Fastfetch, frameworks, prompt +engines, environment tools) stay curated and supported without a tier. Discover +shows Recommended, Recommended + Enhanced (the default) or all curated tools (V). +Installs are explicit: tools are selected (Space, or the R/E presets), reviewed +with their exact commands, and confirmed once. Shell hooks are never modified. +Legacy completed onboarding remains complete. The additive `toolsSetupComplete` +boolean uses the existing normalizer/atomic NMSh preference writer. + +Installation recipes are fixed Homebrew package argv, offered only when brew is +available. Unsupported package managers get official-source guidance without +execution; no sudo, shell interpolation, install scripts or custom taps. Each +install shows the command and software-change scope, starts on No, and requires +a fresh explicit confirmation. Shared TaskProgress reports actual completion +and failures, with bounded captured diagnostics, detached process ownership and +timeout/disposal cleanup. Installation never configures hooks/providers; after +success availability is rechecked. No tools were installed during development. + +The shared configuration panel implements Starship module toggles only, with +supported-value previews, explicit apply, backup and atomic unknown-preserving +writes. Native fallback works with all tools missing. Tool failures are retained +only within the open browser; persistence across browser reopen, bulk selection, +hook activation detection/setup and additional package managers remain deferred. + +NO_COLOR retains text state cues, Safe glyph mode retains keyboard operation, +and progress respects reduced motion. Extremely small widths necessarily clip +descriptive/help rows; use a wider terminal to review installation/configuration. +No mouse interaction is required. diff --git a/website/src/content/reference/physical-qa.md b/website/src/content/reference/physical-qa.md new file mode 100644 index 00000000..29a526d4 --- /dev/null +++ b/website/src/content/reference/physical-qa.md @@ -0,0 +1,121 @@ +--- +title: "v0.20 physical QA checklist" +description: "Canonical v0.20 physical qa checklist from the v0.20.0 published source." +group: Technical reference +order: 111 +source: "docs/testing/v020-physical-qa.md" +--- + +## Status + +- **v0.20.0 was published on 2026-10-18 with partial physical evidence, by maintainer decision.** It is the first + stable release since v0.18.0 and includes the unpublished v0.19 candidate, so this record covers both + ([v019-physical-qa.md](v019-physical-qa.md) keeps the v0.19 checklist). Items not run below are outstanding + follow-ups, not passes, and the release notes list them as known limitations. +- **Reported acceptance (macOS):** the maintainer physically tested and accepted the first-run starting point + (Reactive Horizon), the guided Setup and the Tools discovery, selection and install flow on macOS on the final + onboarding line (`a469ba8e`, carried unchanged into the release commit). This is the maintainer's reported result, + not an item-by-item record of the checklists below, and it does not stand for any other host or platform. +- **Stabilization evidence:** physical QA of the v0.19 and v0.20 candidates on macOS found the input-ownership, + exact-copy, queue, Setup, task-bar and composer-history issues fixed before release (draft PRs #395 and #396 and the + onboarding QA record, [post-v020-preferences-onboarding.md](post-v020-preferences-onboarding.md)). Their full + retests were not recorded item by item. +- **Not performed:** any physical Linux terminal (including Omarchy/Hyprland and Linux Fish 3.7), WSL 2, iTerm2, + kitty, WezTerm or Windows Terminal; managed Claude against a live account (pass 4); the experimental workspace + regions (1E). Linux stays beta on automated CI (Ubuntu, Fedora). +- **What does not count:** headless PTY fixtures, synthetic keys sent through tmux, and captures of a window driven + that way. Each item needs a person at a real terminal with a real keyboard and pointer. +- **Build under test:** the exact release commit (`v0.20.0`). Build it from a clean checkout (`npm ci && npm run build`) + and record `node dist/index.js --version`. +- **Isolation:** a throwaway `HOME` (or `XDG_CONFIG_HOME`), a temporary working directory, and for managed Claude a + disposable Claude account directory. Never point a probe at a real account; every secret below is fake. + +## Results + +| Pass | Host and version | OS | Shell and version | Commit (`--version`) | Result | Notes | +|---|---|---|---|---|---|---| +| Onboarding, guided Setup and Tools | as reported | macOS | as reported | `a469ba8e` line | Accepted (reported) | maintainer's physical acceptance; not itemized | +| 1 Workspace regions Off (default) | Ghostty | macOS | zsh | | Not recorded | | +| 1 Workspace regions Off (default) | Terminal.app | macOS | zsh | | Not recorded | | +| 1E Experimental regions (optional, not a release gate) | Ghostty, Terminal.app, tmux | macOS | zsh | | Deferred | experimental, Off by default | +| 2 Background tasks | Ghostty | macOS | zsh | | Not recorded | | +| 2 Background tasks | physical Linux terminal | Linux | Fish 3.7 or Bash | | Not run | no physical Linux host | +| 3 Modules and worktrees | Ghostty | macOS | zsh | | Not recorded | | +| 4 Managed Claude | Ghostty | macOS | zsh | | Deferred | Claude plugin toggle off in this release | +| 5 Regression of v0.19 core | Terminal.app | macOS | Bash or Fish | | Not recorded | stabilization fixes from earlier macOS passes | + +## Pass 1 — workspace regions + +The docked sidebar and detail panel are **experimental and Off by default** in v0.20 (maintainer decision, +`workspace-ui-decision.md`). The release gate is that Off is really off and that turning them on and off is safe; +the docked regions themselves (1E) are not a release feature and are recorded as **Deferred** unless someone runs +them. A 1E result never counts as release evidence for anything else. + +- [ ] 1.0a Fresh QA home: `/sidebar`, `/panel`, `/sidebar focus`: each prints that the regions are experimental and + Off and names Settings → Layout → Workspace sidebar & panel; nothing opens, the composer keeps the keyboard, and + `tput cols` is the full width before and after. +- [ ] 1.0b Settings → Layout shows **Workspace sidebar & panel Off** with its experimental description; Setup → Editor + shows the same row. `/keys` says the workspace bindings take no key while it is Off. `/worktrees`, `/sessions`, + `/github` and `/background` work as usual. +- [ ] 1.0c Turn it On in Settings, `/sidebar` then `/panel focus` at 160 columns: both dock and `tput cols` is the + middle width. Turn it Off in Settings: both close at once, focus is back in the composer, and `tput cols` is the full + width again; `/sidebar` opens nothing until it is turned on again. + +### 1E — experimental regions (optional; turn the setting On first; not a release gate) + +- [ ] 1.1 At 200, 120, 100, 89, 80, 70 and 40 columns: `/sidebar`, then `/panel`. Wide: both dock, the transcript + rewraps to the middle and `tput cols` in the shell matches the middle width. 70 and below: `/sidebar` opens a drawer + with the keyboard; `tput cols` is unchanged (opening a drawer never resizes the shell); Esc tucks it and the + composer is never covered while it has the keyboard. No horizontal scroll anywhere. +- [ ] 1.2 Safe glyphs and `NO_COLOR=1`: focus, selection and the active item stay distinguishable without color. +- [ ] 1.3 With the sidebar docked, run `vim` and `less`: the regions hide, the program gets every column, and they come + back with focus where it was after the program exits. +- [ ] 1.4 Inside tmux: the tmux section lists sessions, windows and panes; Enter on another pane switches to it; the + prefix and any `bind -n` keys still reach tmux first (`/keys` names them). +- [ ] 1.5 Enter on a worktree with an empty composer stages `cd -- '…'`; with a draft it says the draft is kept. +- [ ] 1.6 Click the sidebar, the panel and back in the transcript: focus follows; selection and hover in the transcript + land on the right rows with the sidebar docked. +- [ ] 1.7 A `keymap.json` binding Left (empty draft, `"replaces": true`) to `sidebar.focus`: Left with a draft still + edits; with an empty draft it focuses the sidebar; `/keys` lists it. On Terminal.app check the Page Up/Down note. + +## Pass 2 — background tasks + +- [ ] 2.1 `/background run ping -c 100 127.0.0.1` (Linux: `ping 127.0.0.1`): the bar shows it; Up from an empty prompt + focuses the bar; Enter shows live output; End follows again after scrolling back. +- [ ] 2.2 `x` interrupts. With a program that ignores SIGINT (`/background run sh -c 'trap "" INT; sleep 60'`): `x` + says to wait, then offers terminate; holding `x` never jumps to kill. +- [ ] 2.3 A dev server (`/background run npx --yes http-server -p 8765`, or any local one): stop leaves nothing listening + (`lsof -i :8765`). With a real device or emulator: `adb logcat` stops and `adb devices` still works. +- [ ] 2.4 Detach the window while a task runs, reattach with `/resume`: the task and its recent output are there and + nothing restarted. End the session: the task ends. +- [ ] 2.5 Task output never appears in the transcript, `/copy`, Copy as Report or `/compare`. +- [ ] 2.6 By default `tail -f somefile` + Enter runs in the foreground at once. After `/background suggestions on`, the + strip offers it; Enter runs it here; `b` backgrounds once; typing another key returns the draft with the key typed. +- [ ] 2.7 While a foreground command reads input, queued commands run and Up/typing go to the program, never the bar. + +## Pass 3 — native modules and worktrees + +- [ ] 3.1 Real projects for .NET, Swift, Zig, Deno, Elixir, Dart, Flutter, Ruby and PHP installed through Homebrew, + asdf, mise or FVM: requested and active versions match what the tools report, and no tool process starts while + collecting (watch with `ps`). +- [ ] 3.2 The build module in a Gradle, Maven, CMake and Bazel project; a Mercurial and a Jujutsu repository. +- [ ] 3.3 `/github`, then `/worktrees` in the same repository: a worktree whose branch has an open PR offers `p`, which + opens that PR; a fork PR with the same branch name is not offered. + +## Pass 4 — managed Claude (disposable account only) + +- [ ] 4.1 `/model` and `/effort` change the managed target; the change shows as requested until Claude confirms it. +- [ ] 4.2 `/mods`: enable and disable a plugin installed in the disposable account; the settings change is Claude's + own; the running managed session reloads it. +- [ ] 4.3 A multi-line command approval shows every line before you answer. + +## Pass 5 — v0.19 core on the integrated build + +Repeat items 1.1–1.15 of [v019-physical-qa.md](v019-physical-qa.md) once on this build in Terminal.app with Bash or +Fish, with the default settings (the experimental workspace regions Off). Running half of them again with the +regions docked belongs to 1E and is optional. + +## After the pass + +Fill the Results table, attach captures of any failure with the exact keys typed, and open an issue for each failure +against v0.20.0. Fixes ship in a patch release; rerun the affected items on that build. diff --git a/website/src/content/reference/platforms.md b/website/src/content/reference/platforms.md new file mode 100644 index 00000000..33ec3f67 --- /dev/null +++ b/website/src/content/reference/platforms.md @@ -0,0 +1,59 @@ +--- +title: "Platform evidence and limitations" +description: "Canonical platform evidence and limitations from the v0.20.0 published source." +group: Technical reference +order: 102 +source: "docs/architecture/platforms.md" +--- + +| Platform | Status | +|---|---| +| macOS | Supported; physically validated on the hosts listed in the QA records. | +| Linux | Supported by automated tests (Ubuntu CI on Node 22 and 26; this run also exercised zsh 5.9, Fish 3.7 and Bash 5.2 on Ubuntu). **Physical terminal validation pending.** | +| WSL 2 | The supported Windows path: Windows terminal host → WSL 2 → Linux Node.js → NMSh → zsh / Fish / Bash. Detection and logic are tested with simulated kernels and environments. **No real WSL physical QA yet.** | +| WSL 1 | Detected and reported as not supported (no Linux kernel; PTY, signal and socket behavior can differ). Nothing is disabled pre-emptively; `nmsh doctor` and `/status` say so. | +| Native Windows | Not supported. A future ConPTY + PowerShell/Nu design is out of scope; zsh is never forced onto Windows. | + +`src/host/platform.ts` separates the **guest** (where Node runs) from the +**terminal host** (which, under WSL, is a Windows application). + +## WSL detection + +Strongest evidence first: the kernel release (`…-microsoft-standard-WSL2` +means WSL 2; `4.4.0--Microsoft` means WSL 1), `/proc/version`, then +`WSL_DISTRO_NAME` / `WSL_INTEROP` (set by WSL 2). Unknown versions say +"version unknown; WSL 2 is the supported path". + +## Linux audit (v0.16) + +| Area | State | +|---|---| +| PTY lifecycle, process groups, signals | Same POSIX path as macOS (node-pty Unix backend). Interrupt, background jobs and shell switching tested live for zsh, Fish and Bash. | +| XDG paths | Config `$XDG_CONFIG_HOME/nmsh` (absolute only) else `~/.config/nmsh`; history import honors `XDG_DATA_HOME` for Fish. | +| Runtime/sockets | `$XDG_RUNTIME_DIR/nmsh` only when it is a private, owned, non-symlink directory with a short enough socket path; otherwise a per-uid temp directory. Windows mounts (`/mnt/c`, drvfs) never qualify because they fail the private-permission check. | +| Shell discovery | zsh: system paths then PATH. Fish: PATH only. Bash: PATH then system, version ≥ 4.4. | +| Clipboard | `wl-copy` (Wayland) or `xclip`/`xsel` (X11). WSLg provides Wayland; without it `/copy` explains what is missing. | +| Notifications | `notify-send` when installed and a desktop session (D-Bus, Wayland or X11) is reachable; otherwise unsupported, as before. | +| Package managers (v0.17) | `/tools` builds typed executable + argv plans for APT, DNF, pacman, zypper (chosen from `/etc/os-release` ID/ID_LIKE, WSL included) and Homebrew. Only curated, factual package names have a plan; everything else is shown as manual. Non-root plans are explicitly `sudo -n …` (never a password prompt) and name the command to run yourself if sudo needs one. | +| Open helper (v0.17) | `xdg-open`, then `wslview`; neither is required. | +| Install/uninstall | Same source-checkout + `npm link` model; `nmsh uninstall` works from XDG paths and refuses while live sessions run. | +| Config transfer | Exports contain no host paths, so they move between macOS and Linux. | +| Agent detection, history | Platform-independent. | +| Temp files | All bootstrap and helper directories are private `mkdtemp` directories, removed on exit; tests fail on leaks. | + +A container without a UTF-8 locale makes zsh treat non-ASCII names as +non-alphanumeric; one existing zsh test depends on a UTF-8 locale, as CI has. + +## Evidence levels (v0.17) + +These are different claims and are not interchangeable. + +| Platform | Supported | Tested in CI | Physically validated | Notes | +|---|---|---|---|---| +| macOS | Primary | Yes (macOS runner) | Yes (maintainer) | | +| Linux | Beta | Ubuntu 24.04 (full suite; zsh, Fish and Bash installed) and a Fedora subset | No | Package-manager plans, clipboard/notification/open fallbacks and XDG paths run from deterministic fixtures. | +| WSL 2 | Beta, documented limits | Fixtures only (detection, distro, Windows Terminal facts via `WT_SESSION`, mount handling, fallbacks); no WSL runner | No | NMSh inside WSL is a Linux environment. | +| WSL 1 | Detected and reported, not supported | Fixtures | No | | +| Native Windows | Unsupported | n/a | n/a | | + +Package-name tables for APT, DNF, pacman and zypper are curated from distribution naming and covered by unit tests; they were not installed on real machines in this release. diff --git a/website/src/content/reference/privacy.md b/website/src/content/reference/privacy.md new file mode 100644 index 00000000..d2c91331 --- /dev/null +++ b/website/src/content/reference/privacy.md @@ -0,0 +1,64 @@ +--- +title: "Privacy and local data" +description: "Canonical privacy and local data from the v0.20.0 published source." +group: Technical reference +order: 109 +source: "docs/privacy.md" +--- + +NMSh is local-first. There is no account, no cloud service, no telemetry and +no network access on the typing path. Network use is limited to explicit +release checks (`/update`, opt-in background checks) and package-manager +commands you confirm. + +## What NMSh stores, all under its own config directory + +| Data | Contents | Control | +|---|---|---| +| `config.json` | Settings | `/settings`, `nmsh config` | +| `sessions/` | Transcripts you see in `/resume` (command text and output) | retention setting, `/clear`, delete files | +| `history-deletions.json(.d)` | IDs of history entries you deleted | — | +| `agent-activity.json` | Per-agent durations, counts, days used, last 50 run times and exit codes | `/agents off`, `/agents reset` | +| `tool-installs.json` | Tools NMSh installed for you, and how | removed on uninstall through NMSh | +| `completion-specs/` | Only specs you put there | your files | +| `context-packs/` | Context Packs you installed, with their sha256 | `nmsh packs remove`, `/prompt` → Packs | +| `agent-context/claude-status-line.json` | The exact `statusLine` text NMSh added to Claude Code's settings, so it can remove exactly that | `nmsh agent-status remove` | +| `theme-bridge/` | Theme Bridge's environment files, generated themes and ownership ledger (bat and Helix themes live in those tools' own theme directories, recorded in the ledger) | `/theme-bridge` → Remove managed setup | + +Runtime sockets and spools live in a private runtime directory and hold live +session streams until a journal acknowledges them. Each prompt's stream event +includes the shell's command names and its allowlisted context environment (see +below), never credential values. When Claude Code reports to NMSh, its last +status (model, effort, context-window and rate-limit use, cost, repository, +session name; its session id only as a short digest) is kept there too, in a +private file per NMSh session that is pruned when stale. + +## Context modules + +Prompt, Rail and Status Strip modules read local files and a few environment +values only while a visible module needs them, and only on your machine. The +live shell reports an allowlisted set of non-secret variables (for example +`PATH`, `AWS_PROFILE`, `KUBECONFIG`, `VIRTUAL_ENV`) and only the *presence* of +credential variables such as `AWS_SECRET_ACCESS_KEY`. Nothing is uploaded, no +cloud API is called and nothing in a project is executed to learn about it. +Values marked display-only (memory, battery, agent status, cloud account ids) +never enter transcripts or exported settings. The +[Context Modules guide](architecture/context-modules.md) lists every capability +and what it reads. + +## What NMSh does not record + +- Environment variables beyond the context allowlist above, credential values, + secrets, tokens, clipboard contents. +- Agent conversations, prompts or responses; agent detection uses only the + program name. +- Terminal output for analytics; session notices carry only program name, + exit code, duration and cwd. +- Commands excluded from history by the shell's own rules (leading space, + zsh HISTORY_IGNORE, Bash HISTCONTROL/HISTIGNORE) or deleted by you. + +## Shell configuration + +NMSh never edits `.zshrc`, `.bashrc` or `config.fish`. Its shell bootstraps are +private per-session files that load your own configuration first. Diagnostics +read shell configuration files as text and never execute them. diff --git a/website/src/content/reference/prompt-customization.md b/website/src/content/reference/prompt-customization.md new file mode 100644 index 00000000..a6ccab44 --- /dev/null +++ b/website/src/content/reference/prompt-customization.md @@ -0,0 +1,133 @@ +--- +title: "Native prompt customization" +description: "Canonical native prompt customization from the v0.20.0 published source." +group: Technical reference +order: 104 +source: "docs/architecture/prompt-customization.md" +--- + +The NMSh Native prompt is customized along independent axes. None of them is a +preset of the others, and each persists separately in `config.json`. + +| Axis | Setting | What it changes | +| --- | --- | --- | +| Style | `nmsh.style` + `nmsh.styleProfiles` | Geometry and visual construction | +| Theme | `nmsh.palette` | Semantic module colors | +| Vibrance | `nmsh.vibrance` | Strength and separation of theme colors | +| Chroma | `presentation.*` | Optional color or gradient treatment | +| Motion | `presentation.motion/speed/curve/direction` | Optional animation of Chroma | + +## Modules and surfaces + +`/prompt` manages native modules and their routing to Main Prompt, Right Context, +Context Rail or Hidden; Auto uses each module's preferred surface. Existing saved +left/right placement remains valid. The Rail has its own presentation controls +and uses the shared native painter. See [Context Modules](context-modules.md) for +facts, discovery safety and the distinction between these surfaces. + +`/shell` edits the same shell module's visibility and Left/Right placement as +`/prompt`. Hidden, When not default and Always control display only; they do not +switch shells. Changing Side preserves Hidden. When not default compares the +current session backend with the configured default for new sessions. + +## Styles and their settings + +Every style renders the same semantic modules through one cell painter +(`src/prompt/powerline.ts`). That painter supplies Nerd and Safe glyphs, left +and right (mirrored) modules, one- and two-line layouts, narrow fitting and +historical replay for all styles. `/prompt` shows only the controls that change +the selected style. + +| Style | Look | Controls | +| --- | --- | --- | +| Powerline | Filled segments with shaped joins | Start, Connector, Connector fade (with a gap), Fade colors (with a fade), Gap, End, Padding | +| Soft | Filled pills, or one connected capsule | Caps, Layout, Gap (separated only), Padding, Fill (Filled / Subtle) | +| Minimal | Colored text, no fills | Separator, Spacing, Bold | +| Outline | Outlined segments, no fills | Outline (Rounded / Square / Angle), Layout, Gap (separated only), Padding | +| Breadcrumb | A text trail with one filled anchor | Separator, Anchor (first / last / none), Spacing | +| Compact | Dense filled cells, no gaps | Ends, Padding, Seams | +| Ribbon | One band; each module is colored text on it | Slant, Ends, Padding, Band | + +Powerline keeps its long-standing storage (the Main Prompt geometry fields plus +root `gap`/`spacing`), so existing Powerline prompts render exactly as before. +Every other style owns a profile under `nmsh.styleProfiles. diff --git a/website/src/lib/docs.ts b/website/src/lib/docs.ts new file mode 100644 index 00000000..9027bd23 --- /dev/null +++ b/website/src/lib/docs.ts @@ -0,0 +1,29 @@ +import { getCollection } from 'astro:content'; +export async function documents() { + const guides = await getCollection('guides'); + const reference = await getCollection('reference'); + return [ + ...guides.map(entry => ({ ...entry, slug: entry.id })), + ...reference.map(entry => ({ ...entry, slug: `reference/${entry.id}` })), + ].sort((left, right) => left.data.order - right.data.order); +} +export const docGroups = ['Start here', 'Daily work', 'Make it yours', 'Understand the system', 'Technical reference']; + +export const groupId = (title: string) => title.toLowerCase().replaceAll(' ', '-'); +export function docNavigation(entries: Awaited>) { + const referenceGroups = [ + { title: 'Runtime and compatibility', slugs: ['architecture', 'shell-adapter', 'platforms', 'session-interaction'] }, + { title: 'Context and appearance', slugs: ['context-modules', 'prompt-customization', 'theme-bridge'] }, + { title: 'Workflows and integrations', slugs: ['configured-completion', 'optional-tools', 'terminal-workflow', 'background-tasks', 'workspace-keymap', 'github-workspace', 'copy-actions'] }, + { title: 'Project and validation', slugs: ['privacy', 'security', 'accessibility', 'contributing', 'verification', 'physical-qa', 'changelog', 'onboarding'] }, + ]; + const referenceSlugs = new Set(referenceGroups.flatMap(group => group.slugs.map(slug => `reference/${slug}`))); + if (entries.some(entry => entry.data.group === 'Technical reference' && !referenceSlugs.has(entry.slug))) throw new Error('Add the new technical reference to its documentation navigation category.'); + return docGroups.map(title => ({ + title, id: groupId(title), + articles: entries.filter(entry => entry.data.group === title && title !== 'Technical reference'), + children: title === 'Technical reference' ? referenceGroups.map(group => ({ + title: group.title, id: groupId(group.title), articles: entries.filter(entry => group.slugs.includes(entry.slug.replace('reference/', '')) && entry.slug.startsWith('reference/')), + })).filter(group => group.articles.length) : [], + })).filter(group => group.articles.length || group.children.length); +} diff --git a/website/src/lib/site.ts b/website/src/lib/site.ts new file mode 100644 index 00000000..0b34008b --- /dev/null +++ b/website/src/lib/site.ts @@ -0,0 +1,14 @@ +import release from '../data/release.json'; +import launch from '../data/launch.json'; +export { release, launch }; +export const launchLabel = `v${launch.version}${launch.state === 'candidate' ? ' preview' : ''}`; +export const site = { + name: 'notMyShell', shortName: 'NMSh', creator: 'raiseCatError', + url: 'https://nmsh.raisecaterror.com', repo: 'https://github.com/raiseCatError/notMyShell', + description: 'A richer way to use the terminal. Edit commands in a persistent composer, search your output and resume live sessions, with your real shell underneath.', +}; +export const sourceUrl = (path: string) => `${site.repo}/blob/${launch.commit}/${path}`; +export const sourceInstall = launch.state === 'candidate' + ? `git clone https://github.com/raiseCatError/notMyShell.git\ncd notMyShell\ngit checkout --detach ${launch.commit}\nnpm install\nnpm run build\nnpm link\nnmsh` + : `git clone --branch v${launch.version} https://github.com/raiseCatError/notMyShell.git\ncd notMyShell\nnpm install\nnpm run build\nnpm link\nnmsh`; +export const dateLabel = (date: string | Date) => new Date(date).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric', timeZone: 'UTC' }); diff --git a/website/src/pages/404.astro b/website/src/pages/404.astro new file mode 100644 index 00000000..f0118e66 --- /dev/null +++ b/website/src/pages/404.astro @@ -0,0 +1,6 @@ +--- +import Base from '../layouts/Base.astro'; +--- + +
    404 / No page at this path

    A small detour.

    That page is not here. The handbook is a good place to get back on track.

    + diff --git a/website/src/pages/architecture.astro b/website/src/pages/architecture.astro new file mode 100644 index 00000000..932cdc29 --- /dev/null +++ b/website/src/pages/architecture.astro @@ -0,0 +1,13 @@ +--- +import Base from '../layouts/Base.astro'; +import { sourceUrl, site } from '../lib/site'; +--- + +
    Architecture / A clear division of responsibility

    A frontend.
    Not a replacement.

    NMSh improves the interaction around your commands. The shell still interprets them, and your terminal still owns the window.

    +
    1. Terminal hostWindow, fonts, cells, protocols
    2. NMSh frontendComposer, transcript, context
    3. ShellAdapter + PTYBootstrap, transport, lifecycle
    4. Real shellzsh · Bash 4.4+ · Fish
    +
    Continuity, not reconstruction

    One shell.
    Between commands, too.

    A ShellSession owns a pseudo-terminal running a real persistent shell. Normally a separate local session service owns that PTY; the frontend attaches as a client. An in-process fallback exists.

    Working directory, aliases, functions, environment and jobs persist because the shell persists. Closing a frontend window is different from terminating the service or shell. A saved transcript is a record, not a running process.

    For fullscreen and interactive applications, NMSh suspends its presentation and uses passthrough. The program's real terminal interaction remains authoritative.

    Read the runtime architecture →
    +

    What belongs where.

    Each layer has a job. Keeping those boundaries clear helps explain both the capabilities and the compatibility limits.

    The terminal host

    Owns its window, font rendering, terminal protocols, keyboard forwarding and standard text selection.

    Host compatibility →

    NMSh

    Owns the composer, completion menu, transcript presentation, panels, native context and frontend preferences.

    Core concepts →

    Your shell

    Owns parsing, expansion, aliases, functions, command execution, environment and shell jobs.

    ShellAdapter →
    +
    Local-first, with explicit authority

    Your configuration
    stays yours.

    No account, cloud typing service or telemetry. Local session records can still hold sensitive command output, so review what you export or share.

    1. Configuration is executable.

      Shell rc files and framework code are not silently sourced, merged or edited by an import workflow. Offered edits are exact diffs behind confirmation.

    2. Discovery is not execution.

      Context inspection uses bounded local data. Opening a repository must not run its scripts or credential helpers.

    3. Ownership is evidence.

      Managed files are removed or replaced only while NMSh can prove they still match what it wrote.

    +

    Independent. Open source.

    notMyShell is made by raiseCatError and licensed GPL-3.0-only. No fictional company, commercial tiers or adoption claims. Just a public project, with its decisions and limitations in the open.

    View the source ↗
    +
    + diff --git a/website/src/pages/docs/[...slug].astro b/website/src/pages/docs/[...slug].astro new file mode 100644 index 00000000..a41831ec --- /dev/null +++ b/website/src/pages/docs/[...slug].astro @@ -0,0 +1,11 @@ +--- +import { render } from 'astro:content'; +import Docs from '../../layouts/Docs.astro'; +import { documents } from '../../lib/docs'; +export async function getStaticPaths() { + return (await documents()).map(entry => ({ params: { slug: entry.slug }, props: { entry } })); +} +const { entry } = Astro.props; +const { Content, headings } = await render(entry); +--- + diff --git a/website/src/pages/docs/index.astro b/website/src/pages/docs/index.astro new file mode 100644 index 00000000..db98021d --- /dev/null +++ b/website/src/pages/docs/index.astro @@ -0,0 +1,11 @@ +--- +import Docs from '../../layouts/Docs.astro'; +import { documents, docGroups } from '../../lib/docs'; +import { launch, launchLabel } from '../../lib/site'; +const entries = await documents(); +--- + ({ depth: 2, slug: group.toLowerCase().replaceAll(' ', '-'), text: group }))}> + + +
    {docGroups.map(group =>

    {group}

    )}
    +
    diff --git a/website/src/pages/features.astro b/website/src/pages/features.astro new file mode 100644 index 00000000..3f0b07db --- /dev/null +++ b/website/src/pages/features.astro @@ -0,0 +1,21 @@ +--- +import Base from '../layouts/Base.astro'; +import Media from '../components/Media.astro'; +import { launchLabel } from '../lib/site'; +const groups = [ + { title: 'Interaction', text: 'A composer with its own space. Edit multiline commands, see shell-aware classification and get completion candidates before you submit.', items: ['Bottom, Top and Flow layouts', 'One-line or two-line composer', 'Command status and elapsed time'], link: 'composer', label: 'Composer and input' }, + { title: 'Transcript & sessions', text: 'Work with the record of a command, not just a wall of output. Live sessions preserve a continuing shell when a frontend window detaches.', items: ['Command blocks, folding, find and filter', 'Plain-text copy, without frontend chrome', 'Explicit session discovery and resume'], link: 'sessions', label: 'Sessions and resume' }, + { title: 'Execution workflows', text: 'Queue commands in the same shell, see what is running and review what follows. Keep long-running tools in separate session-owned background tasks.', items: ['Serial queue with pause and reviewed resume', 'Live execution timeline and optional output folding', 'Background task manager and separate live output'], link: 'execution', label: 'Queue and background tasks' }, + { title: 'Appearance', text: 'Start with Recommended, Minimal, Keep Existing or a guided setup. Choose a theme, prompt and layout, then adjust them independently.', items: ['Reactive Horizon first-run selector', 'Theme Studio and opt-in Theme Bridge', 'Native, Starship, Oh My Posh, Powerlevel10k or None', 'Chroma, Vespyr, safe glyphs and reduced motion'], link: 'setup', label: 'Setup and appearance' }, + { title: 'Tools & integrations', text: 'Use the tools you choose, with explicit installation plans and configuration boundaries. NMSh remains a frontend, not a replacement editor.', items: ['Recommended and Enhanced tool tiers with reviewed installs', 'Managed Claude Code beta with native model and effort controls', 'Worktree management and read-only GitHub workspace', 'Optional experimental sidebar and panel, Off by default'], link: 'integrations', label: 'Tools and integrations' }, + { title: 'Context & modules', text: 'Capabilities collect facts; modules turn them into useful context. Route them to the prompt, Context Rail, Right Context or Status Strip.', items: ['Demand-driven scheduling and bounded local reads', 'Project, runtimes, Git, system and local cloud profiles', 'Declarative local Context Packs, not executable plugins'], link: 'context', label: 'Context Engine and modules' }, + { title: 'Control & ownership', text: 'Your shell configuration is executable code, not a file to rewrite casually. Imports are data, and changes have a reviewable boundary.', items: ['No account, telemetry or cloud typing path', 'Ownership ledger for managed generated files', 'Exact diffs and explicit package-manager plans'], link: 'configuration', label: 'Configuration and settings' }, +]; +--- + +
    The product / {launchLabel}

    Keep your shell.
    Change how you work.

    Edit commands, follow execution and work with the results. NMSh gives each part of everyday shell use a place in the interface.

    + {groups.map(group =>

    {group.title}

    {group.text}

    {group.label} →
      {group.items.map(item =>
    • {item}
    • )}
    )} +

    A closer look.

    Product media is being prepared separately. For now, each guide explains the controls, behavior and limits.

    +

    Understand the boundary.

    NMSh is not a shell, emulator, multiplexer or AI-first terminal. It belongs between your terminal host and a persistent real shell.

    How it works →
    +
    + diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro new file mode 100644 index 00000000..f2cfbbe9 --- /dev/null +++ b/website/src/pages/index.astro @@ -0,0 +1,30 @@ +--- +import Base from '../layouts/Base.astro'; +import Media from '../components/Media.astro'; +import Code from '../components/Code.astro'; +import { release, launch, launchLabel, site } from '../lib/site'; +--- + +
    +
    +
    notMyShell / NMSh{launchLabel}
    +
    +

    A richer way to use the terminal.

    +

    Edit commands without losing your place. Search past output, queue the next step and return to live sessions.

    NMSh brings a persistent composer, an interactive transcript and local context to the terminal you already use. Your real shell stays underneath.

    zsh · Bash 4.4+ · Fish   /   Open source

    +
    + +
    +

    Your shell stays yours.

    Aliases, environment and working directory carry on in a persistent shell session.

    More than a new look.

    Edit multiline commands, inspect output blocks and resume work in a live session.

    Local. Under your control.

    No account or telemetry. Review configuration changes before NMSh applies them.

    +
    +
    01 / The composer

    Room for the command
    you’re working on.

    A persistent editor gives your input its own space. Keep it at the bottom, move it to the top, or place it after the newest output in Flow.

    1. Write more than one line.

      Native multiline editing, with Shift+Enter and a Ctrl+J fallback.

    2. See what the shell knows.

      Semantic command highlighting and completion without executing unfinished input.

    Meet the composer →
    + +
    +
    + +
    02 / The transcript

    The command ended.
    Its output still matters.

    Return to command blocks, fold long output, search for a result and copy plain text. Live sessions let a shell keep working after its window closes.

    NMSh adds structure around program output. It does not rewrite or recolor what a program prints.

    Explore the transcript →
    +
    +
    03 / Make it your environment

    Useful context.
    Optional character.

    Route native modules where they make sense. Choose your palette and prompt provider. Keep Vespyr and motion, or choose a quieter view. The frontend adapts; the shell stays the shell.

    Context with boundaries

    Project, runtime, Git and system facts come from bounded local capabilities. Opening a repository is not permission to execute its code.

    Context Engine →

    Appearance with ownership

    Theme Studio shapes NMSh. Opt-in Theme Bridge can carry that palette to supported tools through reviewed, managed files.

    Themes and Chroma →
    See the full product →
    +
    Start small

    Your familiar commands.
    A different way to work.

    Homebrew handles installation on macOS. Source builds support macOS, Linux beta and WSL 2, with Node.js 22+.

    {release.tapVersion !== `v${launch.version}` &&

    The tap currently provides {release.tapVersion}. The installation guide includes a pinned source build of this {launchLabel}.

    }Requirements and installation →
    +
    Planned · Future Learn Mode

    Learn the shell. By using it.

    A future guide-and-practice experience for Bash and zsh. Real understanding, with isolation that must be proven before real-shell practice is offered.

    Explore the idea
    +
    + diff --git a/website/src/pages/install.astro b/website/src/pages/install.astro new file mode 100644 index 00000000..77b94da4 --- /dev/null +++ b/website/src/pages/install.astro @@ -0,0 +1,19 @@ +--- +import Base from '../layouts/Base.astro'; +import Code from '../components/Code.astro'; +import { release, launch, launchLabel, sourceInstall, sourceUrl } from '../lib/site'; +const sections = ['Homebrew', 'From source', 'Requirements', 'Update', 'Uninstall', 'Troubleshooting']; +--- + +
    Get started / {launchLabel}

    Keep your shell.
    Add NMSh.

    Start it in the terminal you already use. You do not need an account or a different login shell.

    +
    +

    Homebrew #

    Recommended on macOS. Homebrew manages the installation and its dependencies.

    + {release.tapVersion !== `v${launch.version}` && } +

    Build from source #

    {launch.state === 'candidate' ? 'This checks out the exact public candidate commit used by these docs. It is not a published release. The final tag will replace this checkpoint after release validation.' : 'This checks out the verified published release tag.'} Run these commands in an ordinary shell, outside NMSh.

    Allow the required node-pty native install step. Depending on the platform, a C/C++ build toolchain and Python may be needed. The source distribution is not an npm registry installer.

    +

    Requirements #

    ComponentRequirement / evidence
    Node.js{release.requirements.node} for NMSh
    Shellzsh (default), Bash 4.4+ or Fish. macOS Bash 3.2 is too old.
    macOSPrimary platform. In v0.20.0 the first-run setup and Tools flow were physically accepted; other checklist items were not recorded.
    LinuxBeta. Ubuntu automated suite and Fedora subset; no physical validation yet.
    WindowsWSL 2 beta path only. Run Linux Node inside WSL. Native Windows and WSL 1 unsupported.

    Keep a real shell as your system/login shell. Do not set NMSh using chsh. An optional terminal launch command starts the frontend without changing your shell identity.

    Detailed shell and host compatibility →

    +

    Update #

    For a Homebrew install, /update directs you back to Homebrew rather than modifying its Cellar. For a clean official source checkout, /update shows the latest stable plan; /update apply applies it with build verification and rollback on failure. Review the plan first.

    {launch.state === 'candidate' &&

    The detached candidate checkout above is for review. Do not use the stable updater to advance between candidate commits. Use a fresh checkout of the next explicitly reviewed commit instead.

    } +

    Uninstall #

    Finish live work first; closing a window can leave its session running. Homebrew installations are removed through Homebrew:

    For source installations, preview NMSh's ownership-aware removal plan:

    The source uninstaller removes only launcher links it proves belong to NMSh and refuses while live sessions run. Do not casually delete the config directory: saved sessions and preferences live there. Review any separate data-removal choice explicitly.

    +

    Troubleshooting #

    GUI terminals can inherit a different PATH. For Ghostty, the canonical guide gives this pattern, using paths you replace with your actual installed locations:

    Do not paste placeholder paths unchanged. Confirm the Node executable, launcher location and host configuration before changing a launch command.

    See troubleshooting, the canonical install instructions and release limitations. There are no official downloadable native installers advertised here.

    +
    +
    + diff --git a/website/src/pages/learn.astro b/website/src/pages/learn.astro new file mode 100644 index 00000000..dd005320 --- /dev/null +++ b/website/src/pages/learn.astro @@ -0,0 +1,13 @@ +--- +import Base from '../layouts/Base.astro'; +import Media from '../components/Media.astro'; +--- + +
    Planned · Future Learn Mode

    Learn the shell.
    Understand the command.

    Read a lesson, try it, then work out why it behaved that way. A proposed place to learn Bash and zsh through practice, with skills you can use in any ordinary terminal.

    + +
    An idea, not a shipped feature

    A guide beside you.
    Room to try.

    NMSh already keeps input, output and context together. Learn Mode would use that space to teach Bash and zsh themselves, rather than a collection of NMSh shortcuts.

    On wide terminals, a lesson guide could sit beside a practice area. Smaller terminals would use adaptive tabs or a toggle, keeping both reading and practice usable.

    1. Understand the concept.

      Short explanations, clear objectives, layered hints and a searchable course-like reading experience.

    2. Try it, then inspect.

      Practice with files, pipes, quoting and scripts. Feedback should explain the actual result, not reward memorizing one exact command.

    3. Return with your progress.

      Versioned checkpoints, unfinished practice files and resumable learning, designed to stay local.

    +

    Skills for an ordinary shell.

    The initial teaching shells proposed are Bash and zsh. Their differences must be taught honestly; Fish is deferred. No course, command or practice terminal is available from this page.

    Find your footing

    Terminals and shells. Paths and directories. Files, permissions and how to ask for help.

    Proposed foundations

    Connect ideas

    Streams and pipes. Quoting, variables, exit status and inspecting mistakes instead of guessing.

    Proposed practice

    Make something work

    Write and edit small shell scripts, debug them, and apply the concepts in practical challenges.

    Proposed scripting
    +
    Safety is an architectural gate

    Practice should not put
    your computer at risk.

    A temporary folder is not a secure sandbox. A real shell can reach other files, credentials, processes and the network. Isolation has to be enforced outside the lesson.

    Verified isolated real shell

    Real Bash or zsh practice would require proven OS-enforced containment, a bounded practice filesystem, no host home or credential mounts, no network by default, resource limits and reliable cleanup.

    Explicitly labeled simulation

    If strong isolation cannot be verified on a platform, the proposal requires a clearly labeled simulator or disabled execution. It must never silently fall back to your ordinary host shell. Simulation must not claim full Bash/zsh compatibility.

    Dangerous demonstrations remain simulated. A command blacklist or confirmation prompt is educational feedback, not the security boundary.

    +
    See the wider roadmap →
    +
    + diff --git a/website/src/pages/llms.txt.ts b/website/src/pages/llms.txt.ts new file mode 100644 index 00000000..e6037317 --- /dev/null +++ b/website/src/pages/llms.txt.ts @@ -0,0 +1,4 @@ +import type { APIRoute } from 'astro'; +import { documents } from '../lib/docs'; +import { site, release, launch, launchLabel } from '../lib/site'; +export const GET: APIRoute = async () => new Response(`# notMyShell (NMSh)\n\n> A terminal frontend over a persistent real zsh, Bash 4.4+ or Fish shell. Not a replacement shell or terminal emulator. Independent open-source software by raiseCatError, GPL-3.0-only.\n\nWebsite baseline: ${launchLabel}, source ${launch.commit}, verified ${launch.verifiedAt}. Latest published stable: ${release.latest.tag}, ${release.latest.date}. ${launch.state === 'candidate' ? 'A candidate is not a published release; final release integration and physical QA remain launch gates. ' : `The website documents the published v${launch.version} release; physical-terminal evidence is partial (macOS first-run setup and Tools accepted; no physical Linux or WSL pass). `}Learn Mode is a future proposal, not available functionality.\n\n## Documentation\n${(await documents()).map(entry => `- [${entry.data.title}](${site.url}/docs/${entry.slug}/): ${entry.data.description}`).join('\n')}\n\n## Project\n- [Release and baseline status](${site.url}/releases/#launch-baseline)\n- [Roadmap](${site.url}/roadmap/)\n- [Future Learn Mode](${site.url}/learn/)\n- [Source](${site.repo})\n`, { headers: { 'Content-Type': 'text/plain; charset=utf-8' } }); diff --git a/website/src/pages/releases/[tag].astro b/website/src/pages/releases/[tag].astro new file mode 100644 index 00000000..ba41db35 --- /dev/null +++ b/website/src/pages/releases/[tag].astro @@ -0,0 +1,11 @@ +--- +import { getCollection, render } from 'astro:content'; +import Base from '../../layouts/Base.astro'; +import { dateLabel, release } from '../../lib/site'; +export async function getStaticPaths() { return (await getCollection('releases')).map(entry => ({ params: { tag: entry.data.tag }, props: { entry } })); } +const { entry } = Astro.props; +const { Content } = await render(entry); +--- + +
    Published release / {dateLabel(entry.data.date)}

    {entry.data.tag}

    {entry.data.tag === release.latest.tag ? 'Latest stable release.' : `Historical release. Latest stable is ${release.latest.tag}.`} The original publication's caveats and limitations are preserved below.

    + diff --git a/website/src/pages/releases/index.astro b/website/src/pages/releases/index.astro new file mode 100644 index 00000000..5cb1fc0d --- /dev/null +++ b/website/src/pages/releases/index.astro @@ -0,0 +1,24 @@ +--- +import Base from '../../layouts/Base.astro'; +import { release, launch, launchLabel, dateLabel, sourceUrl, site } from '../../lib/site'; +--- + +
    Releases / Sources and history

    Know what you’re running.

    {launch.state === 'candidate' ? `This site is prepared for v${launch.version}. ` : ''}The latest published stable release is {release.latest.tag}, dated {dateLabel(release.latest.date)}.

    +
    +

    {launchLabel}

    + {launch.state === 'candidate' ?

    The product pages and handbook describe cumulative public code at {launch.commit.slice(0, 8)}. It includes v0.19 and v0.20 stabilization, queue and Copy Actions refinements, and production-wired Reactive Horizon onboarding. It is not a published v0.20 release.

    :

    The handbook and technical references are pinned to the verified published v{launch.version} tag.

    } + {launch.state === 'candidate' ?

    The Setup guide follows the controls and defaults in this exact public checkpoint. Physical-terminal acceptance and the final tag remain launch gates.

    :

    v{launch.version} is the first stable release since v0.18.0 and includes the v0.19.0 candidate, which was never published separately. Physical-terminal evidence is partial; the physical QA record lists what was and was not tested.

    } +

    {release.tapVersion === release.latest.tag ? `Homebrew provides ${release.tapVersion}.` : `Homebrew currently provides ${release.tapVersion}, not ${release.latest.tag}.`} Installation instructions {launch.state === 'candidate' ? 'distinguish the tap from the exact candidate source build.' : 'cover Homebrew and the tagged source build.'} Read the canonical changelog →

    +
    +
    + +
    +

    {release.latest.tag}

    Latest published stable
    +

    Published {dateLabel(release.latest.date)}. The original release notes below include the actual changes and their recorded compatibility limits.

    + +

    Sources you can check

    Version, date and historical notes come from a checked-in GitHub release snapshot, verified {release.verifiedAt}. The handbook uses the source of the verified release tag. Normal builds work offline; refreshing either source is an explicit review step.

    +

    {launch.state === 'candidate' ? 'Candidate changelog' : 'Changelog'} ↗ · Roadmap and proposals →

    +
    +
    +
    + diff --git a/website/src/pages/roadmap.astro b/website/src/pages/roadmap.astro new file mode 100644 index 00000000..8d3a403c --- /dev/null +++ b/website/src/pages/roadmap.astro @@ -0,0 +1,12 @@ +--- +import Base from '../layouts/Base.astro'; +import { launch, launchLabel, site, sourceUrl } from '../lib/site'; +--- + +
    Direction / Built in the open

    What’s taking shape.

    A clear view of the product, the work being finished and the ideas that still need testing.

    +

    Shipped in v0.20.0.

    The {launchLabel} release brings the existing frontend together with the terminal workflow, a reviewed first run, background tasks and expanded context. Release notes and history →

    Released

    Commands, output and continuity

    A persistent composer and shell, live sessions, serial queue, execution timeline, output selection, Copy Actions, reviewed reports and comparisons.

    Execution workflows →
    Released

    Work alongside the shell

    Session-owned background tasks, managed Claude Code beta, worktree tools, read-only GitHub inspection and a larger native module catalog. The experimental workspace sidebar remains Off by default.

    Tools and workspaces →
    +

    Still being validated.

    Shipping a feature and proving it on every host are separate steps. Automated tests are not physical acceptance.

    Released

    First-run setup and Tools

    The Reactive Horizon starting point, guided Setup and the shared Tools flow shipped in v0.20.0 and were physically accepted on macOS.

    Setup and first run →
    Validation

    Real hosts beyond macOS

    Item-by-item checklist passes in Ghostty and Terminal.app, and any physical Linux, Omarchy or WSL 2 pass, are still open. Linux remains beta on automated CI.

    Physical QA record →
    +

    Future proposals.

    Directions worth investigating, not scheduled commitments. Scope, feasibility, security and design approval can change what eventually ships.

    Planned · Future

    Learn the command line by doing

    Guided Bash/zsh lessons, practice files, helpful feedback and resumable progress. Strong real-shell isolation must be verified, or execution must be explicitly simulated or disabled.

    Explore future Learn Mode →
    Research / proposal

    Broader integrations, without blurred authority

    Additional managed-agent adapters, workflow tool integrations and stronger isolation for executable mods require independent capability and security evidence.

    +

    Reviewed {launch.verifiedAt}. See the source roadmap and public issues for the full context. An open issue is not automatically a product commitment.

    +
    + diff --git a/website/src/pages/sitemap.xml.ts b/website/src/pages/sitemap.xml.ts new file mode 100644 index 00000000..bd1bc89e --- /dev/null +++ b/website/src/pages/sitemap.xml.ts @@ -0,0 +1,7 @@ +import type { APIRoute } from 'astro'; +import { documents } from '../lib/docs'; +import { site, release } from '../lib/site'; +export const GET: APIRoute = async () => { + const paths = ['/', '/features/', '/install/', '/docs/', '/learn/', '/roadmap/', '/releases/', '/architecture/', ...release.history.map(item => `/releases/${item.tag}/`), ...(await documents()).map(entry => `/docs/${entry.slug}/`)]; + return new Response(`${paths.map(path => `${site.url}${path}`).join('')}`, { headers: { 'Content-Type': 'application/xml; charset=utf-8' } }); +}; diff --git a/website/src/scripts/client.ts b/website/src/scripts/client.ts new file mode 100644 index 00000000..aa533f79 --- /dev/null +++ b/website/src/scripts/client.ts @@ -0,0 +1,151 @@ +const themeButton = document.querySelector('#theme-toggle'); +function updateThemeButton() { + const light = document.documentElement.dataset.theme === 'light'; + themeButton?.setAttribute('aria-label', `Switch to ${light ? 'dark' : 'light'} theme`); + document.querySelector('meta[name="theme-color"]')?.setAttribute('content', light ? '#f7f6f2' : '#101114'); +} +if (themeButton) { + themeButton.hidden = false; + updateThemeButton(); + themeButton.addEventListener('click', () => { + const theme = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'; + document.documentElement.dataset.theme = theme; + try { localStorage.setItem('nmsh-theme', theme); } catch {} + updateThemeButton(); + }); +} + +const feedback = document.querySelector('#feedback'); +let feedbackTimer: ReturnType; +function announce(message: string) { + if (!feedback) return; + clearTimeout(feedbackTimer); + feedback.textContent = message; + feedback.classList.add('visible'); + feedbackTimer = setTimeout(() => feedback.classList.remove('visible'), 5000); +} +document.querySelectorAll('pre').forEach(pre => { + if (!pre.querySelector('code')) return; + const wrapper = document.createElement('div'); + wrapper.className = 'code-wrap'; + pre.before(wrapper); + wrapper.append(pre); + pre.tabIndex = 0; + if (!pre.hasAttribute('aria-label')) pre.setAttribute('aria-label', 'Code example'); + const button = document.createElement('button'); + button.type = 'button'; + button.className = 'copy-button'; + button.textContent = 'Copy'; + button.setAttribute('aria-label', 'Copy code'); + button.addEventListener('click', async () => { + try { + await navigator.clipboard.writeText(pre.querySelector('code')?.textContent || ''); + announce('Copied to clipboard.'); + } catch { + announce('Copy unavailable. Select the code and copy it manually.'); + } + }); + wrapper.append(button); +}); + +const sectionLinks = [...document.querySelectorAll('.docs-toc nav a[href^="#"], .toc-mobile nav a[href^="#"]')]; +const sections = [...new Set(sectionLinks.map(link => document.getElementById(decodeURIComponent(link.hash.slice(1)))).filter((section): section is HTMLElement => section !== null))]; +if (sections.length) { + let scheduled = false; + let active = ''; + function updateSection() { + scheduled = false; + const marker = (document.querySelector('.site-header')?.getBoundingClientRect().bottom || 0) + 24; + const target = document.querySelector(':target'); + let current = sections[0].id; + let nearest = -Infinity; + for (const section of sections) { + const top = Math.floor(section.getBoundingClientRect().top); + if (top <= marker && (top > nearest || (top === nearest && section === target))) { current = section.id; nearest = top; } + } + if (current === active) return; + active = current; + for (const link of sectionLinks) { + if (decodeURIComponent(link.hash.slice(1)) === current) link.setAttribute('aria-current', 'location'); + else link.removeAttribute('aria-current'); + } + } + function scheduleSection() { + if (scheduled) return; + scheduled = true; + requestAnimationFrame(updateSection); + } + document.addEventListener('scroll', scheduleSection, { passive: true }); + window.addEventListener('resize', scheduleSection); + window.addEventListener('hashchange', scheduleSection); + updateSection(); +} + +interface SearchData { url: string; meta: { title?: string }; excerpt: string } +interface SearchModule { search: (query: string) => Promise<{ results: { data: () => Promise }[] }> } +const dialog = document.querySelector('#docs-search'); +const trigger = document.querySelector('#search-open'); +const input = document.querySelector('#search-input'); +const searchStatus = document.querySelector('#search-status'); +const results = document.querySelector('#search-results'); +let searchModule: Promise | undefined; +let generation = 0; +let searchTimer: ReturnType; +function openSearch() { dialog?.showModal(); input?.focus(); } +if (trigger && dialog) { + trigger.hidden = false; + const modifier = document.querySelector('#search-modifier'); + if (modifier && !/Mac|iPhone|iPad/.test(navigator.userAgent)) modifier.textContent = 'Ctrl'; + trigger.addEventListener('click', openSearch); + document.querySelector('#search-close')?.addEventListener('click', () => dialog.close()); + dialog.addEventListener('close', () => trigger.focus()); + dialog.addEventListener('keydown', event => { + if (event.key === 'Escape') { event.preventDefault(); dialog.close(); } + }); + dialog.addEventListener('click', event => { + if (event.target !== dialog) return; + const bounds = dialog.getBoundingClientRect(); + if (event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom) dialog.close(); + }); + document.addEventListener('keydown', event => { + if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') { event.preventDefault(); openSearch(); } + }); +} +document.querySelector('#search-form')?.addEventListener('submit', event => event.preventDefault()); +input?.addEventListener('input', () => { + clearTimeout(searchTimer); + const current = ++generation; + const query = input.value.trim(); + results?.replaceChildren(); + if (!searchStatus || !results) return; + if (!query) { searchStatus.textContent = 'Search guides and versioned technical references.'; return; } + searchStatus.textContent = 'Searching…'; + searchTimer = setTimeout(async () => { + try { + const modulePath = '/pagefind/pagefind.js'; + searchModule ||= import(/* @vite-ignore */ modulePath).catch(error => { searchModule = undefined; throw error; }); + const pagefind = await searchModule; + const response = await pagefind.search(query); + const matches = await Promise.all(response.results.slice(0, 8).map(result => result.data())); + if (current !== generation) return; + searchStatus.textContent = matches.length ? `${response.results.length} result${response.results.length === 1 ? '' : 's'}.` : 'No results. Try a shorter term or browse the documentation.'; + for (const match of matches) { + const item = document.createElement('li'); + const link = document.createElement('a'); + const url = new URL(match.url, location.origin); + if (url.origin !== location.origin) continue; + link.href = url.pathname + url.hash; + const title = document.createElement('span'); + title.textContent = match.meta.title || 'Documentation'; + const excerpt = document.createElement('p'); + const parsed = new DOMParser().parseFromString(match.excerpt, 'text/html'); + excerpt.textContent = parsed.body.textContent; + link.append(title, excerpt); + item.append(link); + results.append(item); + } + } catch { + if (current === generation) searchStatus.textContent = 'Search index unavailable. Browse all documentation below. Local development search needs a production build.'; + } + }, 140); +}); diff --git a/website/src/styles/global.css b/website/src/styles/global.css new file mode 100644 index 00000000..d2a139b9 --- /dev/null +++ b/website/src/styles/global.css @@ -0,0 +1,450 @@ +@layer reset, base, layout, components, docs, responsive; + +@layer reset { + *, *::before, *::after { box-sizing: border-box; } + body, h1, h2, h3, h4, p, figure { margin: 0; } + img, video { display: block; max-width: 100%; height: auto; } + button, input { font: inherit; } + button, summary { cursor: pointer; } + a { color: inherit; } + [hidden] { display: none !important; } +} + +@layer base { + :root { + color-scheme: dark; + --bg: #101114; + --surface: #18191e; + --surface-2: #202127; + --text: #edede9; + --muted: #a5a6b0; + --subtle: #82858f; + --line: #30323a; + --accent: #c7b7ee; + --accent-ink: #17121f; + --note: #1c1925; + --focus: #dbcbff; + --header: #101114; + --scroll-track: #18191e; + --scroll-thumb: #7f7199; + --scroll-hover: #a18abd; + --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + --max: 1160px; + --header-height: 76px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + font-size: 16px; + } + :root[data-theme='light'] { + color-scheme: light; + --bg: #f7f6f2; + --surface: #efeee9; + --surface-2: #e7e5e0; + --text: #202126; + --muted: #595b65; + --subtle: #696a72; + --line: #cfcdc8; + --accent: #63458f; + --accent-ink: #fff; + --note: #eeebf3; + --focus: #63458f; + --header: #f7f6f2; + --scroll-track: #e7e5e0; + --scroll-thumb: #8a77a3; + --scroll-hover: #6b5288; + } + html { scroll-padding-top: 100px; scrollbar-gutter: stable; } + * { scrollbar-width: thin; scrollbar-color: var(--scroll-thumb) var(--scroll-track); } + @supports selector(::-webkit-scrollbar) { + * { scrollbar-width: auto; scrollbar-color: auto; } + *::-webkit-scrollbar { width: 8px; height: 8px; } + *::-webkit-scrollbar-track, *::-webkit-scrollbar-corner { background: var(--scroll-track); } + *::-webkit-scrollbar-thumb { background: var(--scroll-thumb); border-radius: 0; } + *::-webkit-scrollbar-thumb:hover { background: var(--scroll-hover); } + } + @media (forced-colors: active) { + * { scrollbar-width: auto; scrollbar-color: auto; } + *::-webkit-scrollbar { width: auto; height: auto; } + *::-webkit-scrollbar-thumb, *::-webkit-scrollbar-thumb:hover { background: CanvasText; } + *::-webkit-scrollbar-track, *::-webkit-scrollbar-corner { background: Canvas; } + } + body { background: var(--bg); color: var(--text); line-height: 1.65; -webkit-font-smoothing: antialiased; } + ::selection { background: var(--accent); color: var(--accent-ink); } + :focus-visible { outline: 2px solid var(--focus); outline-offset: 5px; border-radius: 2px; } + #main:focus { outline: none; } + a { text-underline-offset: .22em; text-decoration-thickness: 1px; transition: color 140ms ease; } + a:hover { color: var(--accent); } + button { color: inherit; } + h1, h2, h3, h4 { line-height: 1.12; font-weight: 550; letter-spacing: -.04em; text-wrap: balance; } + h1 { font-size: clamp(2.75rem, 6.2vw, 5.5rem); } + h2 { font-size: clamp(2rem, 3.5vw, 3.1rem); } + h3 { font-size: 1.4rem; } + p { text-wrap: pretty; } + p + p { margin-top: 1.1em; } + code, pre, kbd { font-family: var(--mono); } + code:not(pre code) { color: var(--accent); font-size: .88em; overflow-wrap: anywhere; } + kbd { font-size: .7rem; color: var(--muted); } + small { font-size: .8rem; } + hr { border: 0; border-top: 1px solid var(--line); margin: 2rem 0; } +} + +@layer layout { + .container { width: min(var(--max), calc(100% - 80px)); margin-inline: auto; } + .section { padding-block: 100px; border-top: 1px solid var(--line); } + .section-heading { display: grid; grid-template-columns: 1fr 1fr; gap: 80px; align-items: start; margin-bottom: 50px; } + .section-heading p { max-width: 49ch; color: var(--muted); margin-top: 7px; } + .split { display: grid; grid-template-columns: 1fr 1.2fr; gap: 88px; align-items: center; } + .split.reverse { grid-template-columns: 1.2fr 1fr; } + .split-copy { max-width: 45ch; } + .split-copy h2 { margin: 18px 0 24px; } + .split-copy p { color: var(--muted); } + .split-copy .text-link { margin-top: 24px; display: inline-flex; } + .page-hero { padding: 88px 0 68px; } + .page-hero h1 { margin: 22px 0 24px; max-width: 15ch; } + .page-hero .lead { max-width: 62ch; } + .article-layout { display: grid; grid-template-columns: 240px minmax(0, 760px); gap: 80px; padding-bottom: 100px; } + .article-nav { position: sticky; top: 110px; align-self: start; display: grid; gap: 12px; font-size: .9rem; color: var(--muted); } + .article-nav a { text-decoration: none; } + .measure { max-width: 72ch; } + .spaced { margin-top: 32px; } +} + +@layer components { + .muted { color: var(--muted); } + .eyebrow { display: block; font-family: var(--mono); text-transform: uppercase; letter-spacing: .13em; font-size: .68rem; font-weight: 500; color: var(--accent); } + .lead { font-size: clamp(1.05rem, 1.5vw, 1.22rem); line-height: 1.7; color: var(--muted); } + .button { display: inline-flex; align-items: center; justify-content: center; min-height: 46px; padding: 10px 21px; gap: 22px; border: 1px solid var(--accent); background: var(--accent); color: var(--accent-ink); border-radius: 5px; font-size: .88rem; line-height: 1.4; font-weight: 600; text-decoration: none; transition: border-color 150ms; } + .button:hover { background: var(--text); border-color: var(--text); color: var(--bg); } + .button.secondary { background: transparent; border-color: var(--line); color: var(--text); } + .button.secondary:hover { border-color: var(--muted); color: var(--accent); } + .button.small { min-height: 36px; padding: 6px 14px; font-size: .76rem; } + .button:disabled, .button[aria-disabled='true'] { background: var(--surface-2); border-color: var(--line); color: var(--muted); cursor: not-allowed; transition: none; } + .actions { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin-top: 32px; } + .text-link { display: inline-flex; align-items: center; gap: 16px; font-size: .9rem; line-height: 1.4; font-weight: 550; text-decoration: none; } + .actions > .text-link { min-height: 46px; } + .visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; } + .text-link span { color: var(--accent); } + .badge { display: inline-block; border: 1px solid var(--line); border-radius: 3px; padding: 4px 9px; color: var(--accent); font-size: .73rem; font-weight: 500; letter-spacing: .02em; } + .skip-link { position: fixed; top: 10px; left: 10px; transform: translateY(-160%); padding: 8px 16px; background: var(--accent); color: var(--accent-ink); z-index: 100; } + .skip-link:focus { transform: none; } + .site-header { height: var(--header-height); border-bottom: 1px solid var(--line); position: sticky; top: 0; background: var(--header); z-index: 30; } + .header-inner { --header-max: var(--max); width: min(var(--header-max), calc(100% - 80px)); height: 100%; margin: auto; display: flex; align-items: center; gap: 32px; } + .docs-page .header-inner { --header-max: 1360px; } + .brand { font-size: 1.05rem; font-weight: 600; letter-spacing: -.035em; display: inline-flex; align-items: center; gap: 11px; text-decoration: none; white-space: nowrap; } + .brand img { width: 42px; height: 24px; object-fit: contain; } + .desktop-nav { display: flex; align-items: stretch; height: 100%; gap: 28px; margin-left: auto; } + .desktop-nav a { display: inline-flex; align-items: center; text-decoration: none; font-size: .82rem; line-height: 1; color: var(--muted); position: relative; } + .desktop-nav a:hover, .desktop-nav a[aria-current] { color: var(--text); } + .desktop-nav a[aria-current]::after { content: ''; position: absolute; height: 2px; background: var(--accent); bottom: -1px; left: 0; right: 0; } + .planned-dot { display: inline-block; border: 1px solid var(--accent); width: 5px; height: 5px; border-radius: 50%; margin-left: 4px; } + .header-actions { display: flex; align-items: center; gap: 18px; } + .icon-button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-width: 34px; min-height: 36px; background: none; border: 0; padding: 3px; color: var(--muted); } + .icon-button:hover { color: var(--accent); } + .icon-button > span { font-size: 1.3rem; line-height: 1; } + .search-trigger { display: inline-flex; align-items: center; gap: 8px; min-width: 164px; min-height: 36px; padding: 6px 10px; border: 1px solid var(--line); border-radius: 10px; color: var(--muted); background: var(--surface); font-size: .78rem; line-height: 1; transition: color 140ms, border-color 140ms, background 140ms; } + .search-trigger:hover { color: var(--text); border-color: var(--muted); background: var(--surface); } + .search-trigger svg { flex: 0 0 auto; } + .search-trigger:focus-visible { border-color: var(--focus); } + .search-trigger kbd { display: inline-flex; align-items: center; justify-content: center; gap: 2px; margin-left: auto; min-height: 20px; padding: 3px 5px; border: 1px solid var(--line); border-radius: 4px; font-size: .65rem; line-height: 1; } + .header-github { display: inline-flex; align-items: center; min-height: 36px; gap: 4px; font-size: .8rem; line-height: 1; text-decoration: none; } + .mobile-menu { display: none; } + .hero { padding-block: 76px 72px; } + .hero-top { display: grid; grid-template-columns: 1.2fr 1fr; gap: 72px; align-items: start; margin-bottom: 32px; } + .hero h1 { max-width: 12ch; font-size: clamp(3.4rem, 6vw, 5.6rem); line-height: 1.04; letter-spacing: -.055em; } + .hero h1 em { display: block; color: var(--accent); font-style: normal; } + .hero-copy { max-width: 43ch; } + .hero-copy .lead { color: var(--text); font-size: 1.2rem; line-height: 1.6; letter-spacing: -.015em; } + .hero-explanation { color: var(--muted); font-size: .94rem; line-height: 1.75; } + .hero-meta { display: flex; align-items: center; gap: 20px; margin-bottom: 28px; font-family: var(--mono); font-size: .7rem; color: var(--muted); } + .hero-meta > span { color: var(--accent); } + .hero-meta a { text-decoration: none; } + .hero-note { font-family: var(--mono); font-size: .68rem; color: var(--muted); margin-top: 18px; } + .hero .media { margin-top: 50px; } + .hero .media-frame { max-height: 490px; } + .principles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 48px; padding: 28px 0; border-top: 1px solid var(--line); } + .principles h2 { font-size: .9rem; letter-spacing: -.01em; margin-bottom: 8px; } + .principles p { color: var(--muted); font-size: .82rem; max-width: 36ch; } + .media-frame { width: 100%; aspect-ratio: var(--media-ratio); position: relative; background: var(--surface); border: 1px solid var(--line); border-radius: 8px; overflow: hidden; } + .media-frame > img, .media-frame > video { width: 100%; height: 100%; object-fit: contain; } + .media-pending { height: 100%; display: grid; place-items: center; padding: 40px; background-image: linear-gradient(var(--line) 1px, transparent 1px), linear-gradient(90deg, var(--line) 1px, transparent 1px); background-size: 25% 50%; } + .media-pending::before { content: ''; position: absolute; inset: 20px; border: 1px solid var(--line); border-radius: 2px; pointer-events: none; } + .media-center { display: grid; gap: 12px; text-align: center; background: var(--surface); padding: 26px 34px; max-width: 90%; z-index: 1; } + .media-symbol { color: var(--accent); font-family: var(--mono); font-size: 2.5rem; line-height: 1; font-weight: 300; margin-bottom: 8px; } + .media-center .eyebrow { font-size: .57rem; color: var(--muted); } + .media-title { font-size: clamp(1.1rem, 2vw, 1.7rem); letter-spacing: -.035em; font-weight: 450; line-height: 1.2; } + .media-status { position: absolute; bottom: 12px; left: 50%; transform: translateX(-50%); font-size: .6rem; font-family: var(--mono); background: var(--surface); color: var(--muted); padding-inline: 12px; white-space: nowrap; } + .media figcaption { font-size: .72rem; color: var(--muted); padding: 12px 2px 0; } + .media.compact .media-center { padding: 16px; } + .media.compact .media-title { font-size: 1.15rem; } + .number-list { list-style: none; padding: 0; margin: 32px 0 0; counter-reset: numbered; } + .number-list li { counter-increment: numbered; display: grid; grid-template-columns: 32px 1fr; gap: 14px; padding: 20px 0; border-top: 1px solid var(--line); } + .number-list li::before { content: '0' counter(numbered); font-family: var(--mono); color: var(--accent); font-size: .68rem; padding-top: 3px; } + .number-list strong { font-size: .94rem; font-weight: 550; } + .number-list p { color: var(--muted); font-size: .88rem; margin-top: 6px; } + .feature-row { display: grid; grid-template-columns: .65fr 1.1fr 1fr; gap: 56px; padding: 42px 0; border-top: 1px solid var(--line); align-items: start; } + .feature-row h2 { font-size: 1.4rem; } + .feature-row p { color: var(--muted); font-size: .94rem; } + .feature-row ul { margin: 0; padding-left: 18px; font-size: .86rem; color: var(--muted); } + .feature-row a { color: var(--text); } + .feature-media-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 48px 28px; } + .callout-band { padding: 42px; background: var(--surface); border: 1px solid var(--line); border-radius: 6px; display: flex; align-items: center; justify-content: space-between; gap: 32px; } + .callout-band h2 { font-size: 1.7rem; margin-bottom: 10px; } + .callout-band p { color: var(--muted); max-width: 55ch; font-size: .92rem; } + .command-block { background: var(--surface); border: 1px solid var(--line); border-radius: 6px; min-width: 0; overflow: hidden; } + .command-label { border-bottom: 1px solid var(--line); padding: 12px 20px; font-size: .66rem; color: var(--muted); font-family: var(--mono); text-transform: uppercase; letter-spacing: .1em; } + pre { padding: 24px 72px 24px 24px; overflow-x: auto; background: var(--surface) !important; font-size: .8rem; line-height: 1.9; tab-size: 2; border-radius: 4px; margin: 0; } + .astro-code, .astro-code span { color: var(--shiki-dark) !important; background-color: transparent !important; } + [data-theme='light'] .astro-code, [data-theme='light'] .astro-code span { color: var(--shiki-light) !important; } + .code-wrap { position: relative; min-width: 0; } + .copy-button { position: absolute; top: 12px; right: 12px; font: .65rem var(--mono); padding: 6px 9px; border: 1px solid var(--line); border-radius: 3px; background: var(--surface); color: var(--muted); } + .copy-button:hover { color: var(--text); border-color: var(--muted); } + .note { background: var(--note); border-left: 2px solid var(--accent); padding: 20px 24px; margin-block: 28px; font-size: .86rem; color: var(--muted); } + .note strong { color: var(--text); } + .note p { margin-top: 6px; } + .flow-diagram { list-style: none; padding: 0; display: grid; grid-template-columns: repeat(4, 1fr); margin-block: 40px; } + .flow-diagram li { padding: 26px 20px; border: 1px solid var(--line); border-right: 0; position: relative; } + .flow-diagram li:last-child { border-right: 1px solid var(--line); } + .flow-diagram li:not(:last-child)::after { content: '→'; position: absolute; right: -9px; top: 24px; background: var(--bg); color: var(--accent); z-index: 1; } + .flow-diagram strong { display: block; font-size: .92rem; font-weight: 550; } + .flow-diagram span { display: block; color: var(--muted); font-size: .76rem; margin-top: 8px; } + .roadmap-item { display: grid; grid-template-columns: 155px 1fr; gap: 40px; padding: 30px 0; border-top: 1px solid var(--line); } + .roadmap-item h3 { font-size: 1.3rem; margin-bottom: 12px; } + .roadmap-item p { color: var(--muted); max-width: 68ch; } + .roadmap-item .badge { align-self: start; justify-self: start; } + .release-heading { display: flex; gap: 24px; align-items: center; margin-bottom: 24px; } + .launch-baseline { max-width: 76ch; padding: 28px 0 40px; margin-bottom: 48px; border-block: 1px solid var(--line); } + .launch-baseline h2 { margin-top: 0; } + .release-heading h2 { margin: 0 !important; } + .release-index { display: flex; flex-direction: column; gap: 16px; } + .release-index a { display: flex; justify-content: space-between; border-bottom: 1px solid var(--line); padding-bottom: 16px; text-decoration: none; font-size: .9rem; } + .site-footer { border-top: 1px solid var(--line); padding: 48px max(40px, calc((100% - var(--max)) / 2)); } + .footer-top, .footer-bottom { display: flex; justify-content: space-between; gap: 30px; } + .footer-name { font-size: 1.1rem; letter-spacing: -.04em; text-decoration: none; font-weight: 550; } + .footer-signature { max-width: 43ch; } + .footer-signature p { font-size: .9rem; line-height: 1.65; color: var(--muted); margin-top: 16px; } + .footer-aside { display: block; font-size: .75rem; color: var(--subtle); margin-top: 8px; } + .footer-top nav { display: flex; align-content: start; gap: 16px 26px; font-size: .8rem; flex-wrap: wrap; } + .footer-top nav a { text-decoration: none; } + .footer-bottom { margin-top: 44px; color: var(--muted); font-size: .72rem; } + .footer-bottom a { text-decoration: none; } + .footer-bottom .footer-creator { text-decoration: underline; text-decoration-color: var(--line); } + .footer-bottom .footer-creator:hover { text-decoration-color: currentColor; } + dialog { --search-top: min(10dvh, 88px); width: min(650px, calc(100% - 32px)); max-height: min(760px, calc(100dvh - var(--search-top) - 24px)); margin: var(--search-top) auto 24px; padding: 24px; color: var(--text); background: var(--bg); border: 1px solid var(--line); border-radius: 16px; opacity: 0; transform: translateY(6px); transition: opacity 140ms ease, transform 140ms ease, display 140ms allow-discrete, overlay 140ms allow-discrete; } + dialog[open] { opacity: 1; transform: none; } + dialog::backdrop { background: #000b; opacity: 0; transition: opacity 140ms ease, display 140ms allow-discrete, overlay 140ms allow-discrete; } + dialog[open]::backdrop { opacity: 1; } + @starting-style { dialog[open] { opacity: 0; transform: translateY(6px); } dialog[open]::backdrop { opacity: 0; } } + .search-top { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 18px; } + .search-top h2 { font-size: .82rem; font-weight: 550; letter-spacing: .01em; } + .text-button { background: none; border: 0; font-size: .8rem; padding: 8px; } + .search-field { display: flex; align-items: center; gap: 12px; padding: 14px 16px; background: var(--surface); border: 1px solid var(--line); border-radius: 10px; color: var(--muted); transition: border-color 140ms; } + .search-field:focus-within { border-color: var(--focus); } + .search-field svg { flex: 0 0 auto; } + #search-input { width: 100%; min-width: 0; border: 0; background: transparent; color: var(--text); border-radius: 0; padding: 0; font-size: 1.1rem; line-height: 1.5; letter-spacing: -.015em; } + #search-input:focus-visible { outline: none; } + #search-input::placeholder { color: var(--muted); opacity: 1; } + #search-status { font-size: .75rem; margin-top: 16px; } + #search-results { list-style: none; padding: 0; margin: 20px 0; } + #search-results li { border-top: 1px solid var(--line); } + #search-results a { display: block; padding: 15px 10px; margin-inline: -10px; color: var(--text); text-decoration: none; font-weight: 550; transition: background 120ms; } + #search-results a:hover, #search-results a:focus-visible { background: var(--surface); } + #search-results a > span { color: var(--accent); } + #search-results p { font-size: .8rem; font-weight: 400; color: var(--muted); margin-top: 6px; } + .search-footer { font-size: .8rem; border-top: 1px solid var(--line); padding-top: 16px; } + #feedback { position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%); background: var(--text); color: var(--bg); padding: 12px 20px; border-radius: 5px; max-width: calc(100% - 32px); width: max-content; font-size: .84rem; z-index: 50; opacity: 0; pointer-events: none; } + #feedback.visible { opacity: 1; } +} + +@layer docs { + .docs-grid { display: grid; grid-template-columns: 230px minmax(0, 760px) 180px; gap: 48px; max-width: 1440px; margin-inline: auto; padding-inline: 40px; } + .docs-sidebar { position: sticky; top: var(--header-height); height: calc(100dvh - var(--header-height)); overflow-y: auto; overscroll-behavior-y: contain; scrollbar-gutter: stable; padding: 36px 12px 40px 0; border-right: 1px solid var(--line); font-size: .8rem; } + .sidebar-title { text-decoration: none; display: flex; justify-content: space-between; font-weight: 600; padding-right: 16px; } + .version-label { display: block; font: .65rem var(--mono); color: var(--muted); margin-top: 10px; } + .nav-group { margin-top: 12px; } + .doc-navigation { margin-top: 24px; } + .doc-navigation summary { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 8px; list-style: none; color: var(--text); font-size: .78rem; font-weight: 550; line-height: 1.5; } + .doc-navigation summary::-webkit-details-marker { display: none; } + .doc-navigation summary::after { content: '›'; color: var(--muted); font-size: 1.1rem; line-height: 1; transform: rotate(0deg); } + .doc-navigation details[open] > summary::after { transform: rotate(90deg); } + .nav-children { margin-left: 8px; border-left: 1px solid var(--line); } + .nav-subgroup { margin-left: 2px; } + .nav-subgroup > summary { font-size: .73rem; color: var(--muted); font-weight: 400; } + .nav-group a { display: block; text-decoration: none; color: var(--muted); padding: 6px 10px; border-left: 2px solid transparent; line-height: 1.5; } + .nav-group a[aria-current] { color: var(--accent); background: var(--note); border-left-color: var(--accent); } + .nav-group a:hover { color: var(--text); } + .docs-main { min-width: 0; padding-top: 38px; padding-bottom: 80px; } + .breadcrumbs { font-size: .7rem; display: flex; flex-wrap: wrap; gap: 10px; color: var(--muted); margin-bottom: 38px; } + .breadcrumbs a { text-decoration: none; } + .doc-heading { padding-bottom: 32px; border-bottom: 1px solid var(--line); margin-bottom: 32px; } + .doc-heading h1 { margin: 18px 0 18px; font-size: clamp(2rem, 3.2vw, 3.3rem); } + .doc-heading .lead { font-size: 1.05rem; } + .prose { font-size: .96rem; line-height: 1.85; min-width: 0; overflow-wrap: anywhere; } + .prose h2 { margin: 48px 0 18px; font-size: 1.65rem; } + .prose h3 { margin: 32px 0 14px; font-size: 1.2rem; } + .prose h4 { margin: 24px 0 12px; font-size: 1.05rem; } + .prose > p, .prose li { color: var(--muted); } + .prose a:where(:not(.button)) { color: var(--accent); } + .prose strong { color: var(--text); font-weight: 600; } + .prose ul, .prose ol { padding-left: 23px; margin-block: 20px; } + .prose li + li { margin-top: 9px; } + .prose blockquote { margin: 24px 0; border-left: 2px solid var(--accent); padding: 14px 22px; background: var(--note); color: var(--muted); font-size: .9rem; } + .prose pre, .prose .code-wrap { margin-block: 24px; } + .prose .code-wrap pre { margin: 0; border: 1px solid var(--line); } + .prose .command-block { margin-block: 24px; } + .prose .command-block .code-wrap, .prose .command-block pre { margin: 0; border: 0; } + .prose table { width: 100%; border-collapse: collapse; font-size: .82rem; text-align: left; margin-block: 24px; } + .prose th { color: var(--text); font-weight: 550; } + .prose th, .prose td { padding: 12px 14px; border-bottom: 1px solid var(--line); vertical-align: top; } + .prose th:first-child, .prose td:first-child { padding-left: 0; } + .prose td { color: var(--muted); } + .table-scroll { overflow-x: auto; max-width: 100%; margin-block: 24px; } + .table-scroll table { margin: 0; min-width: 420px; } + .heading-anchor { display: inline; font-family: var(--mono); font-size: .72em; text-decoration: none; margin-left: .5em; color: var(--subtle) !important; } + .heading-anchor:hover { color: var(--accent) !important; } + .heading-anchor:empty::before { content: '#'; } + .doc-provenance { border-top: 1px solid var(--line); margin-top: 50px; padding-top: 22px; font-size: .7rem; color: var(--muted); display: grid; gap: 8px; } + .doc-provenance a { text-underline-offset: 3px; } + .doc-pagination { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; padding-top: 36px; } + .doc-pagination a { display: grid; gap: 6px; text-decoration: none; font-size: .86rem; } + .doc-pagination small { color: var(--muted); font-size: .7rem; } + .doc-pagination a:last-child { text-align: right; } + .docs-toc { align-self: start; position: sticky; top: 112px; max-height: calc(100vh - 140px); overflow-y: auto; padding-bottom: 24px; } + .docs-toc h2 { font-size: .68rem; letter-spacing: .05em; text-transform: uppercase; margin-bottom: 18px; } + .docs-toc a { display: block; text-decoration: none; color: var(--muted); font-size: .7rem; padding: 6px 10px; border-left: 2px solid transparent; line-height: 1.5; } + .docs-toc a:hover { color: var(--accent); } + .docs-toc a[aria-current='location'], .toc-mobile a[aria-current='location'] { color: var(--accent); border-left-color: var(--accent); } + .docs-toc .nested { padding-left: 22px; font-size: .66rem; } + .toc-source { border-top: 1px solid var(--line); margin-top: 24px; padding-top: 18px !important; } + .docs-mobile { display: none; } + .toc-mobile { display: none; } + .docs-index { display: grid; grid-template-columns: 1fr 1fr; gap: 54px 72px; padding-bottom: 100px; } + .docs-index h2 { font-size: 1.45rem; margin-bottom: 18px; } + .docs-index ul { list-style: none; padding: 0; margin: 0; } + .docs-index li { border-top: 1px solid var(--line); } + .docs-index li a { display: block; padding: 13px 0; text-decoration: none; font-size: .88rem; } + .doc-index-arrow { float: right; color: var(--muted); } + .prose .docs-index ul { padding: 0; } + .prose .docs-index h2 { margin-top: 0; } + .prose .docs-index li + li { margin-top: 0; } + .doc-index-intro { margin-bottom: 56px; } + .actions.doc-index-intro { margin-top: 0; margin-bottom: 32px; } + .note.doc-index-intro { margin-top: 0; margin-bottom: 40px; } +} + +@layer responsive { + @media (min-width: 1440px) { .hero { padding-top: 96px; } } + @media (max-width: 1200px) { + .header-inner { gap: 24px; } + .desktop-nav { gap: 20px; } + .header-actions { gap: 10px; } + .header-github, .search-label { display: none; } + .search-trigger { min-width: auto; } + .search-trigger kbd { margin-left: 0; } + .docs-grid { grid-template-columns: 210px minmax(0, 1fr); gap: 40px; } + .docs-toc { display: none; } + .toc-mobile { display: block; font-size: .8rem; color: var(--muted); border-bottom: 1px solid var(--line); padding-bottom: 16px; margin-bottom: 30px; } + .toc-mobile summary { padding: 5px 0; } + .toc-mobile nav { display: grid; gap: 10px; padding-top: 16px; } + .toc-mobile a { text-decoration: none; border-left: 2px solid transparent; padding-left: 10px; } + .split, .split.reverse { gap: 48px; } + .article-layout { gap: 48px; grid-template-columns: 180px minmax(0, 1fr); } + } + @media (max-width: 900px) { + .container, .header-inner { width: calc(100% - 48px); } + .desktop-nav, .header-start { display: none; } + .header-actions { margin-left: auto; gap: 12px; } + .mobile-menu { display: block; font-size: .8rem; } + .mobile-menu summary { list-style: none; padding: 9px; } + .mobile-menu summary::after { content: ' +'; color: var(--accent); } + .mobile-menu[open] summary::after { content: ' −'; } + .mobile-menu nav { position: absolute; right: 24px; top: 65px; display: grid; width: 240px; padding: 16px; background: var(--surface); border: 1px solid var(--line); border-radius: 5px; box-shadow: 0 16px 30px #0003; } + .mobile-menu nav a { text-decoration: none; padding: 11px; display: flex; justify-content: space-between; } + .mobile-menu nav small { color: var(--muted); } + .hero-top { gap: 36px; } + .hero h1 { font-size: 3.6rem; } + .hero-copy { max-width: 37ch; } + .hero-copy .lead { font-size: 1.05rem; } + .hero-explanation { font-size: .86rem; } + .section-heading { gap: 40px; } + .section { padding-block: 72px; } + .split, .split.reverse { grid-template-columns: 1fr 1fr; gap: 36px; } + .principles { gap: 28px; } + .feature-row { grid-template-columns: 1fr 1.4fr; gap: 32px; } + .feature-row ul { grid-column: 2; } + .media-center { padding: 18px; } + .media-center .eyebrow { font-size: .5rem; } + .media-status { font-size: .5rem; } + .site-footer { padding-inline: 24px; } + .docs-grid { padding-inline: 24px; grid-template-columns: 195px minmax(0, 1fr); gap: 28px; } + .article-layout { grid-template-columns: 150px minmax(0, 1fr); gap: 32px; } + .flow-diagram { grid-template-columns: 1fr 1fr; gap: 16px; } + .flow-diagram li { border-right: 1px solid var(--line); } + } + @media (max-width: 650px) { + :root { --header-height: 66px; } + .container, .header-inner { width: calc(100% - 40px); } + .brand { font-size: .98rem; } + .brand img { width: 35px; height: 20px; } + .header-inner { gap: 12px; } + .header-actions { gap: 4px; } + .icon-button { min-width: 32px; } + .search-trigger { min-width: 34px; justify-content: center; padding: 8px; border-color: transparent; } + .search-trigger kbd { display: none; } + .hero { padding-top: 40px; padding-bottom: 48px; } + .hero-top { display: block; } + .hero h1 { font-size: clamp(2.8rem, 11.2vw, 4.2rem); max-width: 12ch; } + .hero-copy { margin-top: 28px; max-width: 48ch; } + .hero-note { font-size: .61rem; } + .hero .media { margin-top: 32px; } + .hero-meta { font-size: .68rem; } + .media-pending { padding: 14px; } + .media-pending::before { inset: 12px; } + .media-center { gap: 8px; padding: 12px 8px; max-width: 94%; } + .media-symbol { font-size: 1.7rem; margin-bottom: 2px; } + .media-title { font-size: 1.1rem; } + .media-status { bottom: 7px; font-size: .48rem; } + .media figcaption { font-size: .67rem; } + .principles { grid-template-columns: 1fr; gap: 24px; } + .principles p { max-width: none; } + .section { padding-block: 56px; } + .section-heading { display: block; margin-bottom: 32px; } + .section-heading p { margin-top: 22px; } + .split, .split.reverse { grid-template-columns: 1fr; gap: 32px; } + .split.reverse .split-copy { grid-row: 1; } + .split-copy { max-width: none; } + .feature-row { display: block; padding-block: 30px; } + .feature-row p { margin-top: 18px; } + .feature-row ul { margin-top: 18px; } + .feature-media-grid { grid-template-columns: 1fr; gap: 32px; } + .callout-band { display: block; padding: 26px; } + .callout-band .button { margin-top: 22px; } + .page-hero { padding: 50px 0 44px; } + .page-hero h1 { font-size: 2.8rem; } + .article-layout { grid-template-columns: minmax(0, 1fr); gap: 32px; padding-bottom: 60px; } + .article-nav { position: static; display: flex; flex-wrap: wrap; gap: 12px 22px; border-bottom: 1px solid var(--line); padding-bottom: 24px; font-size: .8rem; } + .flow-diagram { grid-template-columns: 1fr; } + .flow-diagram li:not(:last-child)::after { content: '↓'; top: auto; right: 24px; bottom: -22px; } + .roadmap-item { grid-template-columns: 1fr; gap: 18px; } + .footer-top, .footer-bottom { flex-direction: column; gap: 24px; } + .footer-bottom { gap: 10px; margin-top: 28px; } + .footer-top nav { gap: 14px 24px; } + .docs-grid { display: block; padding-inline: 20px; } + .docs-sidebar { display: none; } + .docs-main { padding-top: 24px; } + .docs-mobile { display: block; border-bottom: 1px solid var(--line); padding-bottom: 16px; margin-bottom: 24px; font-size: .85rem; } + .docs-mobile summary { padding: 6px 0; } + .docs-mobile nav { max-height: 55vh; overflow-y: auto; margin-top: 12px; } + .docs-mobile .doc-navigation { margin-top: 16px; } + .breadcrumbs { margin-bottom: 28px; font-size: .65rem; } + .prose { font-size: .94rem; } + .prose h2 { margin-top: 38px; font-size: 1.45rem; } + .prose th, .prose td { padding: 10px; } + .doc-heading h1 { font-size: 2.2rem; } + .docs-index { grid-template-columns: 1fr; gap: 40px; } + pre { font-size: .72rem; padding: 22px 60px 22px 16px; } + .copy-button { top: 10px; right: 8px; } + dialog { padding: 20px; } + } + @media (prefers-reduced-motion: reduce) { + *, *::before, *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; } + } +} diff --git a/website/tests/brand.test.mjs b/website/tests/brand.test.mjs new file mode 100644 index 00000000..4358c371 --- /dev/null +++ b/website/tests/brand.test.mjs @@ -0,0 +1,31 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import { test } from 'node:test'; +import sharp from 'sharp'; + +const brand = new URL('../public/brand/', import.meta.url); +test('Vespyr icons preserve transparent backgrounds at tab and touch sizes', async () => { + for (const [filename, size] of [['favicon-16.png', 16], ['favicon-32.png', 32], ['apple-touch-icon.png', 180]]) { + const image = sharp(await readFile(new URL(filename, brand))); + const metadata = await image.metadata(); + assert.equal(metadata.width, size); + assert.equal(metadata.height, size); + assert.equal(metadata.hasAlpha, true); + const { data, info } = await image.ensureAlpha().raw().toBuffer({ resolveWithObject: true }); + assert.equal(data[3], 0); + const opaque = Array.from({ length: info.width * info.height }, (_, index) => data[index * 4 + 3]).filter(alpha => alpha > 0).length; + assert.ok(opaque > size * size * .15 && opaque < size * size * .6); + } + const icon = await readFile(new URL('favicon.svg', brand), 'utf8'); + assert.ok(icon.includes('shape-rendering="crispEdges"')); + assert.ok(!icon.includes(' { + const data = await readFile(new URL('social-preview.png', brand)); + const metadata = await sharp(data).metadata(); + assert.equal(metadata.format, 'png'); + assert.equal(metadata.width, 1200); + assert.equal(metadata.height, 630); + assert.ok(data.length < 180000); +}); diff --git a/website/tests/browser/refinement.spec.mjs b/website/tests/browser/refinement.spec.mjs new file mode 100644 index 00000000..b69cbaa2 --- /dev/null +++ b/website/tests/browser/refinement.spec.mjs @@ -0,0 +1,218 @@ +import { test, expect } from '@playwright/test'; +import launch from '../../src/data/launch.json' with { type: 'json' }; + +test('docs navigation keeps the sidebar stable and leaves history and anchors native', async ({ page }) => { + await page.setViewportSize({ width: 1440, height: 900 }); + await page.goto('/docs/reference/architecture/'); + const sidebar = page.locator('.docs-sidebar'); + const position = await sidebar.evaluate(element => { + const link = element.querySelector('a[href="/docs/reference/context-modules/"]'); + window.scrollTo(0, 800); + element.scrollTop = link.offsetTop - 250; + return element.scrollTop; + }); + expect(position).toBeGreaterThan(200); + const destination = sidebar.locator('a[href="/docs/reference/context-modules/"]'); + await destination.focus(); + await page.keyboard.press('Enter'); + await expect(page).toHaveURL(/\/docs\/reference\/context-modules\/$/); + await expect.poll(() => sidebar.evaluate(element => element.scrollTop)).toBeCloseTo(position, 0); + await expect(sidebar.locator('[aria-current="page"]')).toHaveText('Context modules and packs'); + expect(await sidebar.evaluate(element => getComputedStyle(element, '::-webkit-scrollbar').width)).toBe('8px'); + + await page.evaluate(() => window.scrollTo(0, 600)); + await sidebar.locator('[aria-current="page"]').click(); + expect(await page.evaluate(() => scrollY)).toBe(600); + + await page.goBack(); + await expect(page).toHaveURL(/\/docs\/reference\/architecture\/$/); + await expect.poll(() => page.evaluate(() => scrollY)).toBe(800); + await page.goForward(); + await expect(page).toHaveURL(/\/docs\/reference\/context-modules\/$/); + await expect.poll(() => page.evaluate(() => scrollY)).toBe(600); + + await page.goto('/docs/getting-started/#try-a-familiar-command'); + await expect(page).toHaveURL(/#try-a-familiar-command$/); + const headingTop = await page.locator('#try-a-familiar-command').evaluate(element => element.getBoundingClientRect().top); + expect(headingTop).toBeGreaterThanOrEqual(75); + expect(headingTop).toBeLessThan(150); +}); + +test('docs remain usable with blocked session storage', async ({ page }) => { + await page.addInitScript(() => { + Storage.prototype.getItem = () => { throw new Error('Storage blocked'); }; + Storage.prototype.setItem = () => { throw new Error('Storage blocked'); }; + }); + await page.goto('/docs/getting-started/'); + await page.locator('.docs-sidebar').getByRole('link', { name: 'Composer and input', exact: true }).click(); + await expect(page.getByRole('heading', { level: 1 })).toHaveText('Composer and input'); + await expect(page.locator('.docs-sidebar [aria-current="page"]')).toHaveText('Composer and input'); +}); + +test('branding, product hook and footer use the refined product voice', async ({ page }) => { + await page.goto('/'); + await expect(page.getByRole('heading', { level: 1 })).toHaveText('A richer way to use the terminal.'); + await expect(page.locator('.brand [data-brand-vespyr]')).toHaveCount(1); + await expect(page.locator('.brand img[src="/brand/nmsh-logo.png"]')).toHaveCount(0); + await expect(page.locator('.brand')).toContainText('notMyShell'); + await expect(page.locator('.footer-signature p')).toHaveText('Made with curiosity and care.'); + await expect(page.locator('.footer-aside')).toHaveText('Also meows.'); + await expect(page.locator('.site-footer').getByRole('link', { name: 'raiseCatError', exact: true })).toHaveAttribute('href', 'https://raisecaterror.com'); +}); + +test('search keyboard controls and modal geometry stay deliberate as results change', async ({ page }) => { + await page.goto('/'); + await page.keyboard.press('Control+k'); + const dialog = page.getByRole('dialog'); + const input = page.getByRole('searchbox', { name: 'Search documentation' }); + await expect(input).toBeFocused(); + await dialog.evaluate(element => Promise.all(element.getAnimations().map(animation => animation.finished))); + const inputTop = (await input.boundingBox()).y; + expect(await dialog.evaluate(element => parseFloat(getComputedStyle(element).borderRadius))).toBe(16); + expect(await input.evaluate(element => parseFloat(getComputedStyle(element).borderRadius))).toBe(0); + await input.fill('shell'); + await expect(page.locator('#search-results a').first()).toBeVisible(); + expect((await input.boundingBox()).y).toBeCloseTo(inputTop, 0); + const bounds = await dialog.boundingBox(); + expect(bounds.y + bounds.height).toBeLessThanOrEqual(page.viewportSize().height - 16); + await page.locator('.search-footer').scrollIntoViewIfNeeded(); + await expect(page.locator('.search-footer')).toBeInViewport(); + await page.mouse.click(8, 8); + await expect(dialog).not.toBeVisible(); + await expect(page.getByRole('button', { name: 'Search documentation' })).toBeFocused(); +}); + +test('home stays static and native article transitions leave chrome and anchors alone', async ({ page }) => { + await page.addInitScript(() => { + window.addEventListener('pagereveal', event => { + if (!event.viewTransition) return; + event.viewTransition.ready.then(() => { + window.articleTransition = document.getAnimations().filter(animation => animation.effect.pseudoElement?.startsWith('::view-transition')).map(animation => ({ name: animation.animationName, pseudo: animation.effect.pseudoElement })); + }); + event.viewTransition.finished.then(() => window.articleTransitionFinished = true); + }); + }); + await page.emulateMedia({ reducedMotion: 'no-preference' }); + await page.goto('/'); + expect(await page.locator('.hero').evaluate(element => element.getAnimations({ subtree: true }).length)).toBe(0); + await page.goto('/docs/getting-started/'); + expect(await page.locator('.doc-heading').evaluate(element => getComputedStyle(element).animationName)).toBe('none'); + expect(await page.evaluate(() => window.articleTransition)).toBeUndefined(); + await page.locator('.docs-sidebar a[href="/docs/composer/"]').click(); + await expect(page.getByRole('heading', { level: 1 })).toHaveText('Composer and input'); + await expect.poll(() => page.evaluate(() => window.articleTransition)).toEqual(expect.arrayContaining([ + { name: 'docs-article-exit', pseudo: '::view-transition-old(docs-article-getting-started)' }, + { name: 'docs-article-enter', pseudo: '::view-transition-new(docs-article-composer)' }, + ])); + expect(await page.evaluate(() => window.articleTransition.filter(animation => !animation.pseudo.includes('(docs-article-')))).toEqual([]); + await expect.poll(() => page.evaluate(() => window.articleTransitionFinished)).toBe(true); + const origin = await page.evaluate(() => performance.timeOrigin); + const sectionId = await page.locator('.prose h2').nth(1).getAttribute('id'); + await page.locator(`.docs-toc a[href="#${sectionId}"]`).click(); + await expect(page).toHaveURL(new RegExp(`#${sectionId}$`)); + expect(await page.evaluate(() => performance.timeOrigin)).toBe(origin); + expect(await page.evaluate(() => document.getAnimations().filter(animation => animation instanceof CSSAnimation).length)).toBe(0); + await page.emulateMedia({ reducedMotion: 'reduce' }); + await page.locator('.docs-sidebar a[href="/docs/getting-started/"]').click(); + await expect(page.getByRole('heading', { level: 1 })).toHaveText('Getting started'); + expect(await page.evaluate(() => window.articleTransition)).toBeUndefined(); +}); + +test('documentation CTAs keep readable text and icons in both themes and interaction states', async ({ page }) => { + for (const theme of ['dark', 'light']) { + await page.goto('/docs/'); + await page.mouse.move(0, 0); + await page.evaluate(value => document.documentElement.dataset.theme = value, theme); + const button = page.locator('.prose .button'); + for (const state of ['default', 'hover', 'focus', 'disabled']) { + if (state === 'hover') await button.hover(); + if (state === 'focus') await button.focus(); + if (state === 'disabled') await button.evaluate(element => element.setAttribute('aria-disabled', 'true')); + await button.evaluate(element => Promise.all(element.getAnimations().map(animation => animation.finished))); + const contrast = await button.evaluate(element => { + const style = getComputedStyle(element); + const luminance = color => color.match(/[\d.]+/g).slice(0, 3).map(Number).map(channel => channel / 255).map(channel => channel <= .04045 ? channel / 12.92 : ((channel + .055) / 1.055) ** 2.4).reduce((total, channel, index) => total + channel * [.2126, .7152, .0722][index], 0); + const foreground = luminance(style.color); + const background = luminance(style.backgroundColor); + return { ratio: (Math.max(foreground, background) + .05) / (Math.min(foreground, background) + .05), icon: getComputedStyle(element.querySelector('span')).color, text: style.color }; + }); + expect(contrast.ratio, `${theme} ${state}`).toBeGreaterThanOrEqual(4.5); + expect(contrast.icon).toBe(contrast.text); + } + } +}); + +test('docs categories expand natively, preserve choices and track the current section', async ({ page }) => { + await page.goto('/docs/reference/context-modules/'); + const sidebar = page.locator('.docs-sidebar'); + await expect(sidebar.locator('details:not([open])')).toHaveCount(0); + const current = sidebar.locator('[aria-current="page"]'); + expect(await current.evaluate(element => { + const ancestors = []; + for (let parent = element.parentElement; parent; parent = parent.parentElement) if (parent.tagName === 'DETAILS') ancestors.push(parent.open); + return ancestors.length >= 2 && ancestors.every(Boolean); + })).toBe(true); + await expect(current).toBeInViewport(); + const appearance = sidebar.locator('details[data-doc-group="make-it-yours"]'); + await appearance.locator(':scope > summary').focus(); + await page.keyboard.press('Enter'); + await expect(appearance).not.toHaveAttribute('open'); + await sidebar.evaluate(element => element.scrollTop = element.scrollHeight); + await page.goto('/docs/composer/'); + await expect(page.locator('.docs-sidebar details[data-doc-group="make-it-yours"]')).not.toHaveAttribute('open'); + await expect(page.locator('.docs-sidebar details[data-doc-group="daily-work"]')).toHaveAttribute('open', ''); + await expect(sidebar.locator('[aria-current="page"]')).toBeInViewport(); + const heading = page.locator('.prose h2').nth(1); + const sectionId = await heading.getAttribute('id'); + await page.locator(`.docs-toc a[href="#${sectionId}"]`).click(); + await expect(page).toHaveURL(new RegExp(`#${sectionId}$`)); + await expect(page.locator(`.docs-toc a[href="#${sectionId}"]`)).toHaveAttribute('aria-current', 'location'); + await expect(page.locator('.breadcrumbs')).toContainText('Daily work'); + await page.goto('/docs/#daily-work'); + await expect(page.locator('.docs-toc a[href="#daily-work"]')).toHaveAttribute('aria-current', 'location'); +}); + +test('production metadata points to real social artwork and transparent Vespyr tab icons', async ({ page, request }) => { + await page.goto('/'); + await expect(page.locator('meta[property="og:image"]')).toHaveAttribute('content', 'https://nmsh.raisecaterror.com/brand/social-preview.png'); + await expect(page.locator('meta[name="twitter:card"]')).toHaveAttribute('content', 'summary_large_image'); + if (launch.state === 'candidate') await expect(page.locator('meta[name="robots"]')).toHaveAttribute('content', 'noindex'); + else await expect(page.locator('meta[name="robots"]')).toHaveCount(0); + const dimensions = await page.evaluate(() => new Promise(resolve => { + const artwork = new Image(); + artwork.onload = () => resolve([artwork.naturalWidth, artwork.naturalHeight]); + artwork.onerror = () => resolve([]); + artwork.src = '/brand/social-preview.png'; + })); + expect(dimensions).toEqual([1200, 630]); + const icons = await page.locator('link[rel="icon"], link[rel="apple-touch-icon"]').evaluateAll(elements => elements.map(element => element.getAttribute('href'))); + expect(icons).toContain('/brand/favicon.svg'); + for (const path of icons) { + const response = await request.get(path); + expect(response.ok()).toBe(true); + expect(response.headers()['content-type']).toMatch(/^image\//); + } + await page.setViewportSize({ width: 320, height: 740 }); + await page.getByRole('button', { name: 'Switch to light theme' }).click(); + await page.getByRole('button', { name: 'Search documentation' }).click(); + const dialog = page.getByRole('dialog'); + await expect(dialog).toBeVisible(); + expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); + const box = await dialog.boundingBox(); + expect(box.x).toBeGreaterThanOrEqual(0); + expect(box.x + box.width).toBeLessThanOrEqual(320); +}); + +test('first-run documentation follows production-wired Horizon at the pinned source', async ({ page }) => { + await page.goto('/docs/setup/'); + await expect(page.getByRole('heading', { name: 'Reactive Horizon', exact: false })).toBeVisible(); + for (const label of ['NMSh Recommended', 'Minimal Terminal', 'Customize Everything', 'Keep Existing']) { + await expect(page.locator('.prose')).toContainText(label); + } + await expect(page.locator('.prose')).toContainText('/startup'); + await expect(page.locator('.prose')).toContainText('brings the real composer into place'); + await expect(page.locator('.doc-provenance').getByRole('link', { name: 'view.ts', exact: true })).toHaveAttribute('href', `https://github.com/raiseCatError/notMyShell/blob/${launch.commit}/src/setup/startup/view.ts`); + await page.goto('/docs/execution/'); + await expect(page.locator('.prose')).toContainText('Fresh Recommended configurations enable typed queue-summary saving'); + await expect(page.locator('.prose')).toContainText('historical Off behavior'); +}); diff --git a/website/tests/browser/site.spec.mjs b/website/tests/browser/site.spec.mjs new file mode 100644 index 00000000..a7446d08 --- /dev/null +++ b/website/tests/browser/site.spec.mjs @@ -0,0 +1,131 @@ +import { test, expect } from '@playwright/test'; +import AxeBuilder from '@axe-core/playwright'; +import launch from '../../src/data/launch.json' with { type: 'json' }; + +test('home explains the product, pending media and intended launch version', async ({ page }) => { + await page.goto('/'); + await expect(page.getByRole('heading', { level: 1 })).toContainText('A richer way to use'); + await expect(page.getByRole('link', { name: 'Get started', exact: true }).first()).toHaveAttribute('href', '/install/'); + await expect(page.locator('[data-media="home-overview"]')).toContainText('Media pending'); + await expect(page.locator('[data-media] img, [data-media] video')).toHaveCount(0); + const label = `v${launch.version}${launch.state === 'candidate' ? ' preview' : ''}`; + await expect(page.getByText(label, { exact: false }).first()).toBeVisible(); +}); + +test('theme persists and remains usable when local storage is blocked', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); + await page.getByRole('button', { name: 'Switch to light theme' }).click(); + await expect(page.locator('html')).toHaveAttribute('data-theme', 'light'); + await page.reload(); + await expect(page.locator('html')).toHaveAttribute('data-theme', 'light'); + await page.addInitScript(() => { Storage.prototype.setItem = () => { throw new Error('Blocked'); }; }); + await page.reload(); + await page.getByRole('button', { name: 'Switch to dark theme' }).click(); + await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); +}); + +test('local docs search returns useful results and closes back to its trigger', async ({ page }) => { + await page.goto('/docs/'); + const trigger = page.getByRole('button', { name: 'Search documentation' }); + await trigger.click(); + await page.getByRole('searchbox', { name: 'Search documentation' }).fill('Theme Bridge'); + await expect(page.locator('#search-results a').first()).toBeVisible(); + await expect(page.locator('#search-results')).toContainText(/theme/i); + await page.getByRole('searchbox').fill('"zzzznoresultxyz"'); + await expect(page.locator('#search-status')).toContainText('No results'); + await page.keyboard.press('Escape'); + await expect(page.getByRole('dialog')).not.toBeVisible(); + await expect(trigger).toBeFocused(); + await trigger.click(); + await page.getByRole('searchbox').fill('Theme Bridge'); + const result = page.locator('#search-results a').first(); + await expect(result).toBeVisible(); + const destination = new URL(await result.getAttribute('href'), page.url()).href; + await result.click(); + await expect(page).toHaveURL(destination); + await page.keyboard.press('Control+k'); + await expect(page.getByRole('searchbox')).toBeFocused(); +}); + +test('copy succeeds and failure gives honest manual instructions', async ({ page, context }) => { + await context.grantPermissions(['clipboard-read', 'clipboard-write']); + await page.goto('/install/'); + await page.getByRole('button', { name: 'Copy code' }).first().click(); + await expect(page.locator('#feedback')).toContainText('Copied'); + expect(await page.evaluate(() => navigator.clipboard.readText())).toContain('brew install'); + await page.evaluate(() => { navigator.clipboard.writeText = async () => { throw new Error('Denied'); }; }); + await page.getByRole('button', { name: 'Copy code' }).first().click(); + await expect(page.locator('#feedback')).toContainText('Select'); +}); + +test('desktop, tablet and mobile preserve layout, links and accessible navigation', async ({ page }) => { + for (const width of [1440, 834, 390]) { + await page.setViewportSize({ width, height: 900 }); + for (const path of ['/', '/features/', '/docs/context/', '/docs/reference/context-modules/', '/learn/']) { + await page.goto(path); + expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); + await expect(page.getByRole('heading', { level: 1 })).toBeVisible(); + } + } + await page.goto('/docs/reference/context-modules/'); + await page.locator('.docs-mobile > summary').click(); + await expect(page.locator('.docs-mobile [aria-current="page"]')).toBeInViewport(); + await page.locator('.mobile-menu summary').click(); + await page.locator('.mobile-menu').getByRole('link', { name: 'Docs', exact: true }).click(); + await expect(page).toHaveURL(/\/docs\//); + await page.locator('.docs-mobile > summary').click(); + await expect(page.locator('.docs-mobile details[data-doc-group]:not([open])')).toHaveCount(0); + await page.locator('.docs-mobile details[data-doc-group="make-it-yours"] > summary').click(); + await page.locator('.docs-mobile').getByRole('link', { name: 'Composer and input', exact: true }).click(); + await expect(page).toHaveURL(/\/docs\/composer\//); + await page.locator('.docs-mobile > summary').click(); + await expect(page.locator('.docs-mobile details[data-doc-group="make-it-yours"]')).not.toHaveAttribute('open'); +}); + +test('planned learning is explicit and documentation works without JavaScript', async ({ browser, page }) => { + await page.goto('/learn/'); + await expect(page.getByText('Planned · Future Learn Mode', { exact: true })).toBeVisible(); + await expect(page.getByRole('link', { name: /proposal.*398/i })).toHaveAttribute('href', 'https://github.com/raiseCatError/notMyShell/issues/398'); + await expect(page.locator('main')).toContainText('temporary folder'); + const context = await browser.newContext({ javaScriptEnabled: false }); + const plain = await context.newPage(); + await plain.goto(new URL('/docs/', page.url()).href); + await expect(plain.locator('.doc-navigation details:not([open])')).toHaveCount(0); + await plain.locator('.docs-index').getByRole('link', { name: 'Getting started', exact: true }).click(); + await expect(plain.getByRole('heading', { level: 1 })).toHaveText('Getting started'); + await context.close(); +}); + +test('reduced motion, skip link and both themes pass automated accessibility checks', async ({ page }) => { + await page.emulateMedia({ reducedMotion: 'reduce' }); + for (const theme of ['dark', 'light']) { + for (const path of ['/', '/features/', '/install/', '/docs/', '/docs/getting-started/', '/learn/', '/roadmap/', '/releases/', '/architecture/', '/404.html', '/docs/reference/context-modules/']) { + await page.goto(path); + await page.evaluate(value => document.documentElement.dataset.theme = value, theme); + const result = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21aa']).analyze(); + expect(result.violations, `${theme} ${path}: ${JSON.stringify(result.violations.map(item => ({ id: item.id, nodes: item.nodes.map(node => node.target) })))}`).toEqual([]); + } + } + await page.goto('/'); + await page.keyboard.press('Tab'); + await expect(page.getByRole('link', { name: 'Skip to content' })).toBeFocused(); +}); + +test('compact contents, heading permalinks, missing search data and 404 remain useful', async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto('/docs/getting-started/'); + await page.locator('.toc-mobile summary').click(); + await page.locator('.toc-mobile').getByRole('link', { name: 'Try a familiar command', exact: true }).click(); + await expect(page).toHaveURL(/#try-a-familiar-command$/); + await page.locator('#try-a-familiar-command .heading-anchor').click(); + await expect(page).toHaveURL(/#try-a-familiar-command$/); + await page.route('**/pagefind/**', route => route.abort()); + await page.getByRole('button', { name: 'Search documentation' }).click(); + await page.getByRole('searchbox').fill('sessions'); + await expect(page.locator('#search-status')).toContainText('Search index unavailable'); + await page.getByRole('button', { name: 'Close Esc' }).click(); + const response = await page.goto('/no-such-nmsh-page/'); + expect(response.status()).toBe(404); + await expect(page.getByRole('heading', { level: 1 })).toHaveText('A small detour.'); +}); diff --git a/website/tests/ci.test.mjs b/website/tests/ci.test.mjs new file mode 100644 index 00000000..13990cf6 --- /dev/null +++ b/website/tests/ci.test.mjs @@ -0,0 +1,27 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; + +const workflow = readFileSync(new URL('../../.github/workflows/ci.yml', import.meta.url), 'utf8'); +function codeRequired(files, event) { + const expression = workflow.match(/print\('code=' \+ str\((.+)\)\.lower\(\)\)/)?.[1]; + const declarations = workflow.match(/^\s+(?:docs_only|website_files|website_only) = .+$/gm)?.map(line => line.trim()).join('\n'); + const input = `import os\nfiles = [${files.map(file => `b${JSON.stringify(file)}`).join(', ')}]\n${declarations}\nprint(${expression})`; + const result = spawnSync('python3', ['-c', input], { encoding: 'utf8', env: { ...process.env, EVENT: event } }); + assert.equal(result.status, 0, result.stderr); + return result.stdout.trim() === 'True'; +} + +test('website-only PRs and pushes skip expensive CLI jobs, mixed changes do not', () => { + for (const event of ['pull_request', 'push']) { + assert.equal(codeRequired(['website/package.json', 'website/src/pages/index.astro'], event), false); + assert.equal(codeRequired(['.github/workflows/website.yml'], event), false); + assert.equal(codeRequired(['.github/workflows/website-deploy.yml'], event), false); + assert.equal(codeRequired(['README.md', 'website/src/layouts/Base.astro'], event), false); + assert.equal(codeRequired(['website/src/pages/index.astro', 'src/app/TerminalApp.ts'], event), true); + assert.equal(codeRequired(['.github/workflows/ci.yml'], event), true); + } + assert.equal(codeRequired(['README.md'], 'pull_request'), false); + assert.equal(codeRequired(['README.md'], 'push'), true); +}); diff --git a/website/tests/copy.test.mjs b/website/tests/copy.test.mjs new file mode 100644 index 00000000..c1178f04 --- /dev/null +++ b/website/tests/copy.test.mjs @@ -0,0 +1,18 @@ +import assert from 'node:assert/strict'; +import { readFile, readdir } from 'node:fs/promises'; +import { test } from 'node:test'; + +const root = new URL('../src/', import.meta.url); +async function ownedFiles(directory) { + const entries = await readdir(directory, { withFileTypes: true }); + return (await Promise.all(entries.map(entry => entry.isDirectory() ? ownedFiles(new URL(`${entry.name}/`, directory)) : new URL(entry.name, directory)))).flat(); +} +test('website-owned prose avoids em dashes and stock marketing language', async () => { + const files = (await Promise.all(['pages/', 'layouts/', 'components/', 'content/guides/'].map(path => ownedFiles(new URL(path, root))))).flat(); + files.push(new URL('lib/site.ts', root), new URL('data/media.json', root)); + for (const file of files) { + const source = (await readFile(file, 'utf8')).replace(/^\s*(```|~~~)[\s\S]*?^\s*\1[^\n]*$/gm, ''); + assert.ok(!source.includes('—'), `Authored prose contains an em dash: ${file.pathname}`); + assert.doesNotMatch(source, /elevate your workflow|seamlessly integrate|unlock the power|reimagine what.s possible|thoughtfully crafted|powerful yet intuitive|a delightful experience|designed for the modern developer/i); + } +}); diff --git a/website/tests/data.test.mjs b/website/tests/data.test.mjs new file mode 100644 index 00000000..08548a6e --- /dev/null +++ b/website/tests/data.test.mjs @@ -0,0 +1,50 @@ +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import { test } from 'node:test'; +import { selectStable, resolveSourceLink, validateMedia, renderReleaseMarkdown, assertReleaseCache, assertFileMembership, planOwnedRemovals } from '../scripts/content.mjs'; + +test('stable selection rejects drafts and prereleases and sorts semantic versions', () => { + const release = (tag, extra = {}) => ({ tag_name: tag, draft: false, prerelease: false, ...extra }); + assert.equal(selectStable([release('v0.9.0'), release('v0.18.0'), release('v0.20.0', { prerelease: true }), release('v1.0.0', { draft: true })]).tag_name, 'v0.18.0'); + assert.throws(() => selectStable([release('v0.19.0-rc.1')])); +}); + +test('source links preserve anchors, map imported docs and leave external URLs alone', () => { + const options = { commit: 'abc123', routes: { 'docs/privacy.md': 'privacy' } }; + assert.equal(resolveSourceLink('../privacy.md#context-modules', 'docs/architecture/context-modules.md', options), '/docs/reference/privacy/#context-modules'); + assert.equal(resolveSourceLink('../../src/app.ts', 'docs/architecture/context-modules.md', options), 'https://github.com/raiseCatError/notMyShell/blob/abc123/src/app.ts'); + assert.equal(resolveSourceLink('#overview', 'README.md', options), '#overview'); + assert.equal(resolveSourceLink('https://example.com/', 'README.md', options), 'https://example.com/'); +}); + +test('media validates semantic IDs and prevents uncaptioned or inaccessible supplied assets', () => { + const slot = { id: 'hero-shell', width: 1440, height: 900, caption: 'Product overview.', altRequirement: 'Describe the genuine composer and transcript.' }; + assert.equal(validateMedia(slot), true); + assert.throws(() => validateMedia({ ...slot, width: 0 })); + assert.throws(() => validateMedia({ ...slot, src: '/media/hero.webp', kind: 'image' })); + assert.equal(validateMedia({ ...slot, src: '/media/hero.webp', kind: 'image', alt: 'NMSh composer below a completed command.' }), true); + assert.throws(() => validateMedia({ ...slot, src: 'https://unreviewed.example/hero.mp4', kind: 'video', alt: 'Overview' })); +}); + +test('check-only release freshness detects edited notes, metadata, history and extra files', () => { + const release = { tag_name: 'v0.18.0', name: 'Release title', body: 'Actual notes.', published_at: '2026-10-13T03:26:49Z', html_url: 'https://github.com/raiseCatError/notMyShell/releases/tag/v0.18.0', draft: false, prerelease: false }; + const cached = { latest: { tag: release.tag_name, version: '0.18.0', date: '2026-10-13', url: release.html_url }, history: [{ tag: release.tag_name, date: '2026-10-13', url: release.html_url }] }; + const contents = { 'v0.18.0.md': renderReleaseMarkdown(release) }; + assert.doesNotThrow(() => assertReleaseCache([release], cached, contents)); + for (const change of [{ body: 'Corrected notes.' }, { name: 'Corrected title' }, { published_at: '2026-10-14T03:26:49Z' }]) { + assert.throws(() => assertReleaseCache([{ ...release, ...change }], cached, contents)); + } + assert.throws(() => assertReleaseCache([release, { ...release, tag_name: 'v0.17.0' }], cached, contents)); + assert.throws(() => assertReleaseCache([release], cached, { ...contents, 'v0.1.0.md': 'Orphan' })); +}); + +test('generated collections cannot silently publish orphan files or lose manifest entries', () => { + assert.doesNotThrow(() => assertFileMembership(['privacy.md'], ['privacy.md'], 'reference')); + assert.throws(() => assertFileMembership(['privacy.md', 'old.md'], ['privacy.md'], 'reference')); + assert.throws(() => assertFileMembership([], ['privacy.md'], 'reference')); + const contents = { 'old.md': 'Previously generated.' }; + const proofs = { 'old.md': createHash('sha256').update(contents['old.md']).digest('hex') }; + assert.deepEqual(planOwnedRemovals(contents, [], proofs), ['old.md']); + assert.throws(() => planOwnedRemovals({ 'old.md': 'User edits.' }, [], proofs)); + assert.throws(() => planOwnedRemovals(contents, [], {})); +}); diff --git a/website/tests/deployment.test.mjs b/website/tests/deployment.test.mjs new file mode 100644 index 00000000..4ed66a0c --- /dev/null +++ b/website/tests/deployment.test.mjs @@ -0,0 +1,35 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { parse } from 'yaml'; + +test('production publishing is manual, release-gated and isolated behind environment approval', async () => { + const workflow = parse(readFileSync(new URL('../../.github/workflows/website-deploy.yml', import.meta.url), 'utf8')); + assert.deepEqual(Object.keys(workflow.on), ['workflow_dispatch']); + assert.equal(workflow.on.workflow_dispatch.inputs.confirmation.required, true); + assert.deepEqual(workflow.permissions, { contents: 'read' }); + assert.match(workflow.jobs.build.if, /github\.ref == 'refs\/heads\/master'/); + assert.match(workflow.jobs.build.if, /inputs\.confirmation == 'nmsh\.raisecaterror\.com'/); + assert.equal(workflow.jobs.build.defaults.run['working-directory'], 'website'); + assert.ok(workflow.jobs.build.steps.some(step => step.run === 'npm run build:launch')); + assert.doesNotMatch(JSON.stringify(workflow.jobs.build), /secrets\./); + const deploy = workflow.jobs.deploy; + assert.equal(deploy.needs, 'build'); + assert.equal(deploy.environment.name, 'nmsh-production'); + const upload = deploy.steps.find(step => step.env?.CLOUDFLARE_API_TOKEN); + assert.ok(upload); + assert.match(upload.run, /wrangler pages deploy dist --project-name nmsh --branch master/); + assert.equal(deploy.steps.filter(step => step.env?.CLOUDFLARE_API_TOKEN).length, 1); + const preflight = upload.run.match(/<<'NODE'\n([\s\S]+?)\nNODE/)[1]; + const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor; + const checkTarget = new AsyncFunction('process', 'fetch', preflight); + const environment = { env: { CLOUDFLARE_ACCOUNT_ID: '0123456789abcdef0123456789abcdef', CLOUDFLARE_API_TOKEN: 'test-only' } }; + const config = { owner: 'raiseCatError', repo_name: 'notMyShell', production_deployments_enabled: false, deployments_enabled: false, preview_deployment_setting: 'none' }; + const response = async () => ({ ok: true, json: async () => ({ success: true, result: { name: 'nmsh', production_branch: 'master', build_config: { root_dir: 'website' }, source: { type: 'github', config } } }) }); + await checkTarget(environment, response); + config.production_deployments_enabled = true; + await assert.rejects(checkTarget(environment, response), /manual-only settings changed/); + for (const job of Object.values(workflow.jobs)) { + for (const step of job.steps) if (step.uses) assert.match(step.uses, /@[a-f\d]{40}$/); + } +}); diff --git a/website/tests/launch.test.mjs b/website/tests/launch.test.mjs new file mode 100644 index 00000000..955b38ae --- /dev/null +++ b/website/tests/launch.test.mjs @@ -0,0 +1,20 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; +import { assertLaunch } from '../scripts/launch.mjs'; + +const candidate = { state: 'candidate', version: '0.20.0', commit: 'a'.repeat(40), ref: 'stabilize/post-v020-slice3', verifiedAt: '2026-10-17' }; +const published = { ...candidate, state: 'published', ref: 'v0.20.0', date: '2026-10-20', url: 'https://github.com/raiseCatError/notMyShell/releases/tag/v0.20.0' }; +const cache = { latest: { tag: 'v0.20.0', date: published.date, url: published.url }, sourceCommit: published.commit }; +test('candidate provenance is buildable but never passes the publication gate', () => { + assert.doesNotThrow(() => assertLaunch(candidate)); + assert.throws(() => assertLaunch(candidate, cache, true), /published/); + assert.throws(() => assertLaunch({ ...candidate, date: published.date })); + assert.throws(() => assertLaunch({ ...candidate, url: published.url })); + assert.throws(() => assertLaunch({ ...candidate, commit: 'main' })); +}); +test('publication requires an exact public tag, commit, date and release URL match', () => { + assert.doesNotThrow(() => assertLaunch(published, cache, true)); + for (const change of [{ commit: 'b'.repeat(40) }, { ref: 'release/v0.20.0' }, { version: '0.19.0' }, { date: '2026-10-19' }, { url: 'https://preview.example/' }]) { + assert.throws(() => assertLaunch({ ...published, ...change }, cache, true)); + } +}); diff --git a/website/tests/markdown.test.mjs b/website/tests/markdown.test.mjs new file mode 100644 index 00000000..82df78fa --- /dev/null +++ b/website/tests/markdown.test.mjs @@ -0,0 +1,29 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { sourceLinks, accessibleMarkup } from '../scripts/markdown.mjs'; +import { createMarkdownProcessor } from '@astrojs/markdown-remark'; +import config from '../astro.config.mjs'; + +test('real configured Markdown rendering adds copyable section anchors without polluting TOC text', async () => { + const renderer = await createMarkdownProcessor({ syntaxHighlight: false, ...config.markdown.processor.options }); + const result = await renderer.render('## Install and launch\n\nStart here.'); + assert.match(result.code, /class="heading-anchor"/); + assert.match(result.code, /href="#install-and-launch"/); + assert.equal(result.metadata.headings[0].text, 'Install and launch'); +}); + +test('historical release links stay at their own tag and h1 notes fit the page hierarchy', () => { + const tree = { type: 'root', children: [{ type: 'heading', depth: 1, children: [] }, { type: 'link', url: 'docs/accessibility/baseline.md', children: [] }] }; + sourceLinks()(tree, { data: { astro: { frontmatter: { tag: 'v0.7.0' } } } }); + assert.equal(tree.children[0].depth, 2); + assert.equal(tree.children[1].url, 'https://github.com/raiseCatError/notMyShell/blob/v0.7.0/docs/accessibility/baseline.md'); +}); + +test('tables wrap once and code examples remain keyboard scrollable', () => { + const tree = { type: 'root', children: [{ type: 'element', tagName: 'table', properties: {}, children: [] }, { type: 'element', tagName: 'pre', children: [] }] }; + accessibleMarkup()(tree); + assert.equal(tree.children[0].tagName, 'div'); + assert.equal(tree.children[0].children[0].tagName, 'table'); + assert.equal(tree.children[0].properties.role, 'region'); + assert.equal(tree.children[1].properties.tabIndex, 0); +}); diff --git a/website/tsconfig.json b/website/tsconfig.json new file mode 100644 index 00000000..adbbd073 --- /dev/null +++ b/website/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist", "node_modules"] +} diff --git a/website/wrangler.jsonc b/website/wrangler.jsonc new file mode 100644 index 00000000..3a0e6b97 --- /dev/null +++ b/website/wrangler.jsonc @@ -0,0 +1,6 @@ +{ + "$schema": "./node_modules/wrangler/config-schema.json", + "name": "nmsh", + "pages_build_output_dir": "./dist", + "compatibility_date": "2026-10-18" +}