diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 85cada01..14e2edcd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,7 +22,7 @@ Before starting work, check: - **[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 +- **GitHub Issues** — [raiseCatError/notMyShell issues](https://github.com/raiseCatError/notMyShell/issues) — public bug reports, proposals and development status ## Branch Model diff --git a/docs/architecture/context-modules.md b/docs/architecture/context-modules.md index 6153e8dc..ba48a067 100644 --- a/docs/architecture/context-modules.md +++ b/docs/architecture/context-modules.md @@ -8,8 +8,8 @@ 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. +> Packs, Status Strip routing and Status Strip 2.0, `/modules` and Claude Code agent context — shipped +> in v0.18.0 and is part of every later release, including the current v0.20.0. ## From capability to presentation diff --git a/website/DESIGN.md b/website/DESIGN.md index a794b7cb..93f76aca 100644 --- a/website/DESIGN.md +++ b/website/DESIGN.md @@ -49,8 +49,7 @@ silently promoted by a build. A check-only sync reports drift without changing f ## Information architecture -Product `/`, Features `/features/`, Install `/install/`, Docs `/docs/`, future Learn -`/learn/`, Roadmap `/roadmap/`, Releases `/releases/`, Architecture `/architecture/`. +Product `/`, Features `/features/`, Install `/install/`, Docs `/docs/`, 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. @@ -70,8 +69,7 @@ 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 +commands, output, playback buttons or product UI. 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 diff --git a/website/LAUNCH.md b/website/LAUNCH.md index 0cdff701..b0f60f83 100644 --- a/website/LAUNCH.md +++ b/website/LAUNCH.md @@ -57,7 +57,7 @@ followed by a successful rerun. None establishes final released-tag physical acc | 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 | +| Learn Mode | Deferred idea, issue #398; not part of v0.20 code | Intentionally not advertised; `/learn/` returns 404 | ## Onboarding integration boundary @@ -105,7 +105,7 @@ reason to call the already integrated Horizon experience a future proposal. - 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. + or removed. Learn Mode is not advertised. - 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 diff --git a/website/README.md b/website/README.md index 3c21a4ba..16686fe3 100644 --- a/website/README.md +++ b/website/README.md @@ -22,7 +22,7 @@ website/ 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 + pages/ Product, install, 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 @@ -161,9 +161,10 @@ not leave a half-fetched refresh. Filesystem write failures still require checki 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. +Recheck issue state and product direction during each release review. The site does +not advertise Learn Mode or any Bash/zsh course: issue #398 is a deferred, unscheduled +idea about explaining NMSh's own interface and stays off the public site. 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 @@ -185,7 +186,6 @@ caption and future alt-text requirements. Every slot is currently intentionally | `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 @@ -197,7 +197,7 @@ No video library, automatic playback or animation bypass is introduced. Match th 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 +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 @@ -379,3 +379,13 @@ References: [disabled Git builds](https://developers.cloudflare.com/pages/config 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. + +## Editorial corrections to pinned references + +Reference pages are synchronized from the tagged release and are never edited by hand +(`validate-content` rejects it). A stale or wrong passage in a tagged document is +corrected in `scripts/editorial.mjs` instead: each entry is pinned to one source commit, +must match exactly once, is applied during `npm run sync:upstream`, is listed in the +provenance manifest and appears on the page as an "Editorial update". Fix the canonical +document on the development branch too, then delete the entry once a tagged release +contains the fix. diff --git a/website/REVIEW.md b/website/REVIEW.md index d6686174..47643729 100644 --- a/website/REVIEW.md +++ b/website/REVIEW.md @@ -87,7 +87,7 @@ not the revised v0.20 baseline. They are retained as historical verification evi ## Shipped for review -Home, Features, Install, documentation index, planned Learn, Roadmap, Releases, +Home, Features, Install, documentation index, 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. @@ -120,7 +120,7 @@ Chrome covers 1440px desktop, 834px tablet and 390px mobile; dark and light them 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. +installation 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 diff --git a/website/scripts/editorial.mjs b/website/scripts/editorial.mjs new file mode 100644 index 00000000..2da29cb6 --- /dev/null +++ b/website/scripts/editorial.mjs @@ -0,0 +1,33 @@ +// Audited corrections to release-pinned references. The tagged source is immutable, so a wrong or +// stale sentence in it is corrected here, shown on the page as an editorial update and recorded in +// the provenance manifest. Each correction applies only to the commit it was written against and +// must match exactly once; remove it once the corrected text ships in a tagged release. +export const corrections = [ + { + id: 'context-modules-release-status', + commit: '65e4de55b2d8f1ad3a734a9d415a353b56b81e2c', + slug: 'context-modules', + note: 'The release-status paragraph said the module platform was still pending acceptance. It shipped in v0.18.0 and is part of v0.20.0.', + from: 'Packs, Status Strip routing and Status Strip 2.0, `/modules` and Claude Code agent context — is in\n> the v0.18 development stack, pending acceptance and release.', + to: 'Packs, Status Strip routing and Status Strip 2.0, `/modules` and Claude Code agent context — shipped\n> in v0.18.0 and is part of every later release, including the current v0.20.0.', + }, + { + id: 'contributing-public-link', + commit: '65e4de55b2d8f1ad3a734a9d415a353b56b81e2c', + slug: 'contributing', + note: 'The tagged document linked to a private GitHub Project. The link now points to the public issue tracker.', + from: '- **GitHub Project** — [NMSh Development](https://github.com/users/raiseCatError/projects/1) — live development status board', + to: '- **GitHub Issues** — [raiseCatError/notMyShell issues](https://github.com/raiseCatError/notMyShell/issues) — public bug reports, proposals and development status', + }, +]; + +export function applyEditorial(slug, commit, body) { + const applied = []; + let text = body; + for (const correction of corrections.filter(item => item.slug === slug && item.commit === commit)) { + if (text.split(correction.from).length !== 2) throw new Error(`Editorial correction ${correction.id} no longer matches exactly once; review or remove it.`); + text = text.replace(correction.from, () => correction.to); + applied.push(correction); + } + return { body: text, applied }; +} diff --git a/website/scripts/sync-upstream.mjs b/website/scripts/sync-upstream.mjs index 744e4eb3..e964b071 100644 --- a/website/scripts/sync-upstream.mjs +++ b/website/scripts/sync-upstream.mjs @@ -3,6 +3,7 @@ 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'; +import { applyEditorial } from './editorial.mjs'; const repository = 'raiseCatError/notMyShell'; const root = new URL('../', import.meta.url); @@ -53,9 +54,9 @@ 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 { body, applied } = applyEditorial(source.slug, commit.sha, 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${applied.length ? `editorial: ${JSON.stringify(applied.map(item => item.note))}\n` : ''}---\n${body}`; + files.push({ source, original, markdown, editorial: applied.map(item => item.id) }); } 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) })); @@ -98,7 +99,7 @@ const snapshot = { 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) })), + files: files.map(file => ({ ...file.source, sourceSha256: sha256(file.original), generatedSha256: sha256(file.markdown), ...(file.editorial.length ? { editorial: file.editorial } : {}) })), }; 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'); diff --git a/website/scripts/validate-content.mjs b/website/scripts/validate-content.mjs index 39c4b759..c8142477 100644 --- a/website/scripts/validate-content.mjs +++ b/website/scripts/validate-content.mjs @@ -2,6 +2,7 @@ import { readFile, access, readdir } from 'node:fs/promises'; import { createHash } from 'node:crypto'; import { validateMedia, assertFileMembership } from './content.mjs'; import { assertLaunch } from './launch.mjs'; +import { corrections } from './editorial.mjs'; const root = new URL('../', import.meta.url); const read = async path => JSON.parse(await readFile(new URL(path, root), 'utf8')); @@ -16,6 +17,9 @@ 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.`); + const expected = corrections.filter(item => item.slug === file.slug && item.commit === manifest.commit).map(item => item.id); + if (JSON.stringify(expected) !== JSON.stringify(file.editorial || [])) throw new Error(`Editorial corrections for ${file.slug} do not match the provenance manifest. Run the upstream sync.`); + for (const item of corrections.filter(item => expected.includes(item.id))) if (!markdown.toString().includes(item.to) || markdown.toString().includes(item.from)) throw new Error(`Editorial correction ${item.id} is not applied exactly.`); } for (const item of release.history) { const markdown = await readFile(new URL(`src/content/releases/${item.tag}.md`, root)); diff --git a/website/src/components/Media.astro b/website/src/components/Media.astro index 1b203c40..bc9468cf 100644 --- a/website/src/components/Media.astro +++ b/website/src/components/Media.astro @@ -16,8 +16,8 @@ if (!slot) throw new Error(`Unknown media slot: ${id}`); ) : slot.src ? ( {slot.alt} ) : ( - diff --git a/website/src/pages/learn.astro b/website/src/pages/learn.astro deleted file mode 100644 index dd005320..00000000 --- a/website/src/pages/learn.astro +++ /dev/null @@ -1,13 +0,0 @@ ---- -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 index e6037317..c96a4e67 100644 --- a/website/src/pages/llms.txt.ts +++ b/website/src/pages/llms.txt.ts @@ -1,4 +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' } }); +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). `}\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- [Source](${site.repo})\n`, { headers: { 'Content-Type': 'text/plain; charset=utf-8' } }); diff --git a/website/src/pages/roadmap.astro b/website/src/pages/roadmap.astro index 8d3a403c..4d7000d1 100644 --- a/website/src/pages/roadmap.astro +++ b/website/src/pages/roadmap.astro @@ -6,7 +6,7 @@ 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.

+

Future proposals.

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

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 index bd1bc89e..f51b222c 100644 --- a/website/src/pages/sitemap.xml.ts +++ b/website/src/pages/sitemap.xml.ts @@ -2,6 +2,6 @@ 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}/`)]; + const paths = ['/', '/features/', '/install/', '/docs/', '/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/styles/global.css b/website/src/styles/global.css index d2a139b9..78902a30 100644 --- a/website/src/styles/global.css +++ b/website/src/styles/global.css @@ -135,7 +135,6 @@ .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); } @@ -186,9 +185,6 @@ .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; } @@ -415,8 +411,6 @@ .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; } diff --git a/website/tests/browser/site.spec.mjs b/website/tests/browser/site.spec.mjs index a7446d08..0d1ecc21 100644 --- a/website/tests/browser/site.spec.mjs +++ b/website/tests/browser/site.spec.mjs @@ -62,7 +62,7 @@ test('copy succeeds and failure gives honest manual instructions', async ({ page 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/']) { + for (const path of ['/', '/features/', '/docs/context/', '/docs/reference/context-modules/']) { await page.goto(path); expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); await expect(page.getByRole('heading', { level: 1 })).toBeVisible(); @@ -83,11 +83,8 @@ test('desktop, tablet and mobile preserve layout, links and accessible navigatio 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'); +test('documentation works without JavaScript', async ({ browser, page }) => { + await page.goto('/docs/'); const context = await browser.newContext({ javaScriptEnabled: false }); const plain = await context.newPage(); await plain.goto(new URL('/docs/', page.url()).href); @@ -100,7 +97,7 @@ test('planned learning is explicit and documentation works without JavaScript', 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/']) { + for (const path of ['/', '/features/', '/install/', '/docs/', '/docs/getting-started/', '/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(); @@ -129,3 +126,15 @@ test('compact contents, heading permalinks, missing search data and 404 remain u expect(response.status()).toBe(404); await expect(page.getByRole('heading', { level: 1 })).toHaveText('A small detour.'); }); + +test('the retired Learn page is not served or discoverable', async ({ page, request }) => { + const response = await page.goto('/learn/'); + expect(response?.status()).toBe(404); + await page.goto('/'); + await expect(page.locator('header nav, footer').getByRole('link', { name: /learn/i })).toHaveCount(0); + await expect(page.locator('main')).not.toContainText(/learn mode|learn the shell/i); + const sitemap = await (await request.get('/sitemap.xml')).text(); + expect(sitemap).not.toContain('/learn'); + const llms = await (await request.get('/llms.txt')).text(); + expect(llms).not.toMatch(/learn mode|\/learn/i); +}); diff --git a/website/tests/editorial.test.mjs b/website/tests/editorial.test.mjs new file mode 100644 index 00000000..457ca10c --- /dev/null +++ b/website/tests/editorial.test.mjs @@ -0,0 +1,23 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { applyEditorial, corrections } from '../scripts/editorial.mjs'; + +test('corrections replace exactly one passage and only for their pinned commit', () => { + const [first] = corrections; + const body = `before\n${first.from}\nafter\n`; + const result = applyEditorial(first.slug, first.commit, body); + assert.equal(result.body, `before\n${first.to}\nafter\n`); + assert.deepEqual(result.applied.map(item => item.id), [first.id]); + assert.equal(applyEditorial(first.slug, 'f'.repeat(40), body).body, body); + assert.equal(applyEditorial('privacy', first.commit, body).body, body); +}); + +test('a correction that no longer matches exactly once fails loudly', () => { + const [first] = corrections; + assert.throws(() => applyEditorial(first.slug, first.commit, 'unrelated\n'), /no longer matches/); + assert.throws(() => applyEditorial(first.slug, first.commit, `${first.from}\n${first.from}\n`), /no longer matches/); +}); + +test('no correction points at a private GitHub Project', () => { + for (const item of corrections) assert.doesNotMatch(item.to, /github\.com\/users\/[^/]+\/projects/); +});