Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/rfc-index.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: RFC index

# Guard (scripts/check_rfc_index.py): every NNNN-*.md in rfcs/ must have a
# README index row, every row must resolve, rows ascend. Keeps merged RFCs
# reachable from the index — the gap this caught post-hoc on 0018.

on:
pull_request:
paths:
- 'rfcs/**'
- 'scripts/check_rfc_index.py'
- '.github/workflows/rfc-index.yml'
push:
branches: [main]
paths:
- 'rfcs/**'
- 'scripts/check_rfc_index.py'

jobs:
index:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check RFC index consistency
run: python3 scripts/check_rfc_index.py
9 changes: 7 additions & 2 deletions rfcs/0017-update-checking.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ notification with a link. No silent downloads, no self-installation.

## Implementation status

Not implemented anywhere. Dasher-Windows has a `UpdateChecker` service
Dasher-GTK ships the check for self-managed builds (`UpdateChecker`, plus the
Settings → Privacy opt-out); Dasher-Windows has a `UpdateChecker` service
(GitHub Releases API + in-app dialog) that is the natural starting point for
the shared behaviour; it is compiled out in `STORE` builds, which matches the
dual-track split.
Expand All @@ -43,7 +44,7 @@ dual-track split.
| Dasher-Apple (iOS) | TestFlight | None (TestFlight notifies) |
| Dasher-Apple (macOS) | GitHub Releases | None |
| Dasher-Windows | GitHub Releases; STORE variant planned | `UpdateChecker` (non-STORE builds only) |
| Dasher-GTK | GitHub Releases (Flatpak + AppImage artifacts) | None |
| Dasher-GTK | GitHub Releases (Flatpak + AppImage artifacts) | Implemented — `UpdateChecker` (self-managed builds; Flatpak skips) + "Check for updates" toggle in Preferences → Privacy |
| Dasher-Android | GitHub Releases APKs; Play Store intended | None |
| dasher-web | Always "latest" by nature of the browser | N/A |

Expand Down Expand Up @@ -206,3 +207,7 @@ Per [RFC 0011](./0011-testing.md): manual verification primarily.
- Decision: Not yet accepted. Dasher-Windows' UpdateChecker is the seed
implementation; no other frontend has shipped the behaviour.
- Open sub-questions: all (see Unresolved questions).

## History

- _2026-09-09_ — _Implementation status corrected: Dasher-GTK ships the check for self-managed builds (UpdateChecker + Privacy opt-out), which the initial table missed._
1 change: 1 addition & 0 deletions rfcs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,4 @@ means "landed on some platforms, not all."
| [0015](./0015-direct-entry-mode.md) | Direct-entry mode (typing into other applications) | proposed | apple, windows, gtk, android, core |
| [0016](./0016-version-in-settings.md) | Report the app version in Settings (Privacy tab for now) | active | apple, windows, gtk, android, web |
| [0017](./0017-update-checking.md) | Update distribution and in-app update checking (dual-track) | proposed | apple, windows, gtk, android, web |
| [0018](./0018-startup-loading-states.md) | Startup loading states — no frozen or black windows | proposed | apple, windows, gtk, android, web, core |
78 changes: 78 additions & 0 deletions scripts/check_rfc_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""Guard: every RFC file has a README index row, and every index row resolves.

Catches the class of bug where an RFC merges without its `rfcs/README.md`
index entry (0018 shipped unreachable from the index; 0019's PR repeated it)
and the reverse — an index row left pointing at a renamed or deleted file.

Run locally from anywhere:
python3 scripts/check_rfc_index.py
Exits 1 with a readable list on any mismatch. Stdlib only.
"""

from pathlib import Path
import re
import sys

ROOT = Path(__file__).resolve().parent.parent
RFCS = ROOT / "rfcs"
README = RFCS / "README.md"

# The template is not an RFC; its pseudo-row in the index is informational
# and not a link, so it is exempt from the link-format contract.
EXCLUDE = {"0000-template.md"}


def main() -> int:
pattern = "[0-9][0-9][0-9][0-9]-*.md"
files = {p.name for p in RFCS.glob(pattern)} - EXCLUDE

if not README.is_file():
print(f"RFC index check failed: {README} not found")
return 1
text = README.read_text(encoding="utf-8")

# Index rows look like:
# | [0018](./0018-startup-loading-states.md) | Title | status | platforms |
row_re = re.compile(r"^\|\s*\[(\d{4})\]\(\./([^)]+)\)", re.M)

problems: list[str] = []
indexed: dict[str, str] = {} # number -> linked filename
order: list[str] = []
for num, fname in row_re.findall(text):
if num in indexed:
problems.append(f"duplicate index row for {num}")
indexed[num] = fname
order.append(num)

# Direction 1: file without a row (or the row points at a different file).
for f in sorted(files):
num = f.split("-", 1)[0]
if num not in indexed:
problems.append(f"rfcs/{f} exists but has no README index row")
elif indexed[num] != f:
problems.append(
f"index row {num} points at {indexed[num]}, but the file is {f}"
)

# Direction 2: row without a file (renamed/deleted RFC).
for num, fname in indexed.items():
if not (RFCS / fname).is_file():
problems.append(f"index row {num} links ./{fname}, which does not exist")

# Ordering: rows should ascend; a shuffled index hides entries from scan-readers.
if order != sorted(order):
problems.append("index rows are not in ascending numeric order: " + ", ".join(order))

if problems:
print("RFC index check failed:")
for p in problems:
print(f" - {p}")
return 1

print(f"RFC index OK ({len(files)} RFC files, {len(indexed)} index rows)")
return 0


if __name__ == "__main__":
sys.exit(main())
Loading