From 57f93e86c91fb102cc1e34be4ec87e58e0d2908e Mon Sep 17 00:00:00 2001 From: Eugene Kalinin Date: Sun, 27 Sep 2026 22:05:01 +0300 Subject: [PATCH 1/4] docs(openspec): archive fix-remote-first-heading change --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/remote-toc/spec.md | 0 .../tasks.md | 0 openspec/specs/remote-toc/spec.md | 59 +++++++++++++++++++ 6 files changed, 59 insertions(+) rename openspec/changes/{fix-remote-first-heading => archive/2026-09-27-fix-remote-first-heading}/.openspec.yaml (100%) rename openspec/changes/{fix-remote-first-heading => archive/2026-09-27-fix-remote-first-heading}/design.md (100%) rename openspec/changes/{fix-remote-first-heading => archive/2026-09-27-fix-remote-first-heading}/proposal.md (100%) rename openspec/changes/{fix-remote-first-heading => archive/2026-09-27-fix-remote-first-heading}/specs/remote-toc/spec.md (100%) rename openspec/changes/{fix-remote-first-heading => archive/2026-09-27-fix-remote-first-heading}/tasks.md (100%) create mode 100644 openspec/specs/remote-toc/spec.md diff --git a/openspec/changes/fix-remote-first-heading/.openspec.yaml b/openspec/changes/archive/2026-09-27-fix-remote-first-heading/.openspec.yaml similarity index 100% rename from openspec/changes/fix-remote-first-heading/.openspec.yaml rename to openspec/changes/archive/2026-09-27-fix-remote-first-heading/.openspec.yaml diff --git a/openspec/changes/fix-remote-first-heading/design.md b/openspec/changes/archive/2026-09-27-fix-remote-first-heading/design.md similarity index 100% rename from openspec/changes/fix-remote-first-heading/design.md rename to openspec/changes/archive/2026-09-27-fix-remote-first-heading/design.md diff --git a/openspec/changes/fix-remote-first-heading/proposal.md b/openspec/changes/archive/2026-09-27-fix-remote-first-heading/proposal.md similarity index 100% rename from openspec/changes/fix-remote-first-heading/proposal.md rename to openspec/changes/archive/2026-09-27-fix-remote-first-heading/proposal.md diff --git a/openspec/changes/fix-remote-first-heading/specs/remote-toc/spec.md b/openspec/changes/archive/2026-09-27-fix-remote-first-heading/specs/remote-toc/spec.md similarity index 100% rename from openspec/changes/fix-remote-first-heading/specs/remote-toc/spec.md rename to openspec/changes/archive/2026-09-27-fix-remote-first-heading/specs/remote-toc/spec.md diff --git a/openspec/changes/fix-remote-first-heading/tasks.md b/openspec/changes/archive/2026-09-27-fix-remote-first-heading/tasks.md similarity index 100% rename from openspec/changes/fix-remote-first-heading/tasks.md rename to openspec/changes/archive/2026-09-27-fix-remote-first-heading/tasks.md diff --git a/openspec/specs/remote-toc/spec.md b/openspec/specs/remote-toc/spec.md new file mode 100644 index 0000000..6854371 --- /dev/null +++ b/openspec/specs/remote-toc/spec.md @@ -0,0 +1,59 @@ +# remote-toc Specification + +## Purpose + +Generates a table of contents for a remote GitHub page (a file or a wiki page) given by +its URL, with anchors identical to the ones GitHub renders for that page. + +## Requirements + +### Requirement: TOC for a remote GitHub page + +For a GitHub file (`/blob/`) URL or a GitHub wiki page URL, the TOC SHALL contain one +entry per heading of the rendered document, in document order, including the first +heading when the document starts with a heading. Each entry SHALL have the form +`* [](#)`, indented by `(level - 1) * indent` spaces (indent is +3 by default). No entry SHALL contain markup of the GitHub page around the document or +link to anything other than a heading anchor of the document. + +#### Scenario: File that starts with a heading + +- **WHEN** the user runs `gh-md-toc https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md` +- **THEN** the output is `Table of Contents`, `=================`, + `* [sitemap.js](#sitemapjs)`, ` * [Installation](#installation)`, + ` * [Usage](#usage)`, ` * [License](#license)`, and + `` (ignoring empty + lines) + +#### Scenario: File with non-English headings + +- **WHEN** the user runs `gh-md-toc https://github.com/ekalinin/envirius/blob/f939d3b6882bfb6ecb28ef7b6e62862f934ba945/README.ru.md` +- **THEN** the first entries are `* [envirius](#envirius)`, ` * [Идея](#идея)`, + ` * [Особенности](#особенности)`, `* [Установка](#установка)` + +#### Scenario: File that does not start with a heading + +- **WHEN** the user runs `gh-md-toc https://github.com/jlevy/the-art-of-command-line/blob/217da3b4fa751014ecc122fd9fede2328a7eeb3e/README-pt.md` +- **THEN** the first entries are + `* [A arte da linha de comando](#a-arte-da-linha-de-comando)`, ` * [Meta](#meta)`, + ` * [Básico](#básico)`, ` * [Uso diário](#uso-diário)` + +#### Scenario: Wiki page + +- **WHEN** the user runs `gh-md-toc https://github.com/ekalinin/nodeenv/wiki/Who-Uses-Nodeenv` +- **THEN** the first entries are `* [Who Uses Nodeenv?](#who-uses-nodeenv)`, + ` * [edx](#edx)`, ` * [OpenStack](#openstack)` + +### Requirement: Remote page among multiple inputs + +When more than one input is given, the entries of a remote page SHALL follow the same +rules, with each link prefixed by the page URL: `* [](#)`. + +#### Scenario: Local file and remote file + +- **WHEN** the user runs `gh-md-toc README.md https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md` +- **THEN** the README entries are followed by + `* [sitemap.js](https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md#sitemapjs)`, + ` * [Installation](https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md#installation)`, + ` * [Usage](https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md#usage)`, + ` * [License](https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md#license)` From 6a690cdba9631eccdbd79f169caaf31af819b234 Mon Sep 17 00:00:00 2001 From: Eugene Kalinin Date: Sun, 27 Sep 2026 22:09:52 +0300 Subject: [PATCH 2/4] docs(openspec): add landing spec and add-landing-page archive --- .../.openspec.yaml | 2 + .../2026-09-27-add-landing-page/design.md | 234 ++++++++++++++ .../2026-09-27-add-landing-page/proposal.md | 69 +++++ .../specs/landing/spec.md | 291 ++++++++++++++++++ .../2026-09-27-add-landing-page/tasks.md | 56 ++++ openspec/specs/landing/spec.md | 291 ++++++++++++++++++ 6 files changed, 943 insertions(+) create mode 100644 openspec/changes/archive/2026-09-27-add-landing-page/.openspec.yaml create mode 100644 openspec/changes/archive/2026-09-27-add-landing-page/design.md create mode 100644 openspec/changes/archive/2026-09-27-add-landing-page/proposal.md create mode 100644 openspec/changes/archive/2026-09-27-add-landing-page/specs/landing/spec.md create mode 100644 openspec/changes/archive/2026-09-27-add-landing-page/tasks.md create mode 100644 openspec/specs/landing/spec.md diff --git a/openspec/changes/archive/2026-09-27-add-landing-page/.openspec.yaml b/openspec/changes/archive/2026-09-27-add-landing-page/.openspec.yaml new file mode 100644 index 0000000..7f2ad57 --- /dev/null +++ b/openspec/changes/archive/2026-09-27-add-landing-page/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-09-27 diff --git a/openspec/changes/archive/2026-09-27-add-landing-page/design.md b/openspec/changes/archive/2026-09-27-add-landing-page/design.md new file mode 100644 index 0000000..9b12820 --- /dev/null +++ b/openspec/changes/archive/2026-09-27-add-landing-page/design.md @@ -0,0 +1,234 @@ +# Design + +## Context + +See proposal.md for motivation and specs/landing/spec.md for requirements. + +Current state that shapes the approach: + +- GitHub Pages is not enabled for the repository; there is no `docs/`, no `gh-pages` + branch, and no user site repository, so the page is a project site under + `/github-markdown-toc/`. +- The version is single-sourced in `gh_toc_version` (`gh-md-toc`). `make release` + and the `--version` bats test both depend on it. +- README installs from `master` (`raw.githubusercontent.com/.../master/gh-md-toc`), + so what a visitor downloads is always master's version. +- Releases are created by hand; `dockerimage.yml` pushes `evkalinin/gh-md-toc:` + when a release is published. Docker Hub tags equal the release tags, which equal + `gh_toc_version`. +- Remote mode is broken for GitHub file URLs (#166); wiki URLs work. Checked with + 0.10.0: `./gh-md-toc README.md https://github.com/ekalinin/nodeenv/wiki/Who-Uses-Nodeenv` + produces a correct TOC for both inputs. +- `tests/tests.bats` pins README's headings, so README-based example output stays + valid while the tests pass. + +## Goals / Non-Goals + +**Goals:** + +- No build tooling in the repository: two hand-written files plus one workflow. +- Deployment needs only the default `GITHUB_TOKEN`, with no extra secrets. +- Every version on the page comes from `gh_toc_version`, with no second copy in the + repository. + +**Non-Goals:** + +- A local build target in `Makefile`; local preview is a plain static file. +- Automated checks of the page in CI (link checking, HTML validation). +- Automatic refresh of example outputs or action versions shown on the page. + +## Decisions + +### Deploy through GitHub Actions, not from a branch + +The workflow uploads the built `site/` as a Pages artifact and deploys it with +`actions/deploy-pages`. + +- Alternative: "Deploy from a branch" with `master:/docs`. Rejected: it serves files + as-is, so the version would have to be copied into the HTML and bumped by hand, + which breaks single-sourcing. +- Alternative: a `gh-pages` branch. Rejected: same version problem, and page changes + would bypass pull requests on `master`. +- Alternative: fetch the version in the browser at runtime. Rejected: an external + request, which the spec forbids. + +### Source folder `site/` + +- Alternative: `docs/`. Rejected: implies documentation (README remains the docs) + and is the folder name for branch-based deploys, which this design does not use. +- Alternative: repository root. Rejected: mixes page files with the tool. + +Files: `site/index.html` (content, sidebar, inline copy script) and +`site/styles.css`. No separate JavaScript file: the copy script is a few lines. + +### Version substitution at deploy time + +`site/index.html` contains the placeholder `{{VERSION}}` (hero, Docker section). The +workflow: + +1. `VERSION="$(./gh-md-toc --version | head -n 1)"` +2. copies `site/` to `_site/` and writes `_site/index.html` with + `sed "s|{{VERSION}}|$VERSION|g" site/index.html > _site/index.html` (a pipe, not + `sed -i`, to stay portable between GNU and BSD sed when run locally) +3. fails if `grep -n '{{' _site/index.html` finds anything + +- Why `--version`: it is the tool's public interface and is covered by the + `--version` bats test. +- Alternative: parse the `gh_toc_version=` line with a regex. Rejected: it depends on + the assignment's formatting, and the `Makefile` regex already requires a + two-digit minor. + +### Triggers track `master`, not releases + +`on: push` to `master` with `paths: [site/**, gh-md-toc, .github/workflows/pages.yml]`, +plus `workflow_dispatch`. No `pull_request` trigger. + +- Why: installation downloads master's `gh-md-toc`, so the page shows the version a + visitor actually gets. A version bump changes `gh-md-toc` and redeploys the page. +- Alternative: `on: release`. Rejected: between a bump and the release, the page + would show an older version than the installation commands download. + +Workflow settings: `permissions: {contents: read, pages: write, id-token: write}`; +`concurrency: {group: pages, cancel-in-progress: false}`; the deploy job uses the +`github-pages` environment. Actions (checked 2026-09-27): `actions/checkout@v7`, +`actions/configure-pages@v6`, `actions/upload-pages-artifact@v5`, +`actions/deploy-pages@v5`. + +### Pages is enabled once by hand + +Pages is enabled manually with Source "GitHub Actions" (Settings -> Pages, or +`gh api -X POST repos/ekalinin/github-markdown-toc/pages -f build_type=workflow`). + +- Alternative: `configure-pages` with `enablement: true`. Rejected: per its + `action.yml`, it requires a token other than `GITHUB_TOKEN` (a PAT or GitHub App), + which would add a secret. + +### Page structure and headings + +One `h1` and `h2`/`h3` sections, so the sidebar matches gh-md-toc's output for a +markdown document with the same headings: + +``` +* [gh-md-toc](#gh-md-toc) + * [Installation](#installation) + * [Why](#why) + * [Usage](#usage) + * [STDIN](#stdin) + * [Local files](#local-files) + * [Remote files](#remote-files) + * [Multiple files](#multiple-files) + * [Auto insert and update TOC](#auto-insert-and-update-toc) + * [GitHub Actions](#github-actions) + * [GitHub token](#github-token) + * [Docker](#docker) + * [Windows](#windows) +``` + +Heading `id`s equal these anchors. The hero is the `h1` "gh-md-toc". Landmarks: +`header` (hero), `nav` (table of contents), `main` (sections), `footer`. + +### Sidebar rendering + +Each TOC line is one `` rendered with `white-space: pre` in a +monospace font. Its visible text is the full markdown line. The markdown syntax +(`* [`, `](#anchor)`) is wrapped in `