diff options
| author | Agando <roduccomm@gmail.com> | 2026-11-06 11:40:54 +0100 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-11-06 11:40:54 +0100 |
| commit | 7bdcd58762c60c9501657b9df2983999f276f8b1 (patch) | |
| tree | 0b6f23e78869a50b2fb5a1cb4d4e535872474c05 | |
| parent | 1983ce5796e45b312b142554cb717db7f8978bc8 (diff) | |
fix(changelog): keep the newest commits of a build; wrap long detail URLs
Symptom: after bodies were rendered, this fix's own commit was missing from
its own entry, and a long URL inside an expanded detail was clipped at the
right edge on a 390 px screen.
Cause: items took group[:MAX_ITEMS] from a CHRONOLOGICAL list, so a 17-commit
build showed its first 14 and dropped the newest three (the count line said
"…and 3 more", hiding which three). And the injected detail CSS set no
overflow-wrap, so a 47-character URL pushed the paragraph to 320 px of
unbreakable text — the document stayed at 0 overflow only because the li
clipped it.
Fix: show group[-MAX_ITEMS:] and label the omitted ones "…and N earlier
commit(s)"; entry.commits follows the same slice. overflow-wrap:anywhere on
detail p/ul. The CSS block is now re-emitted from DETAIL_CSS behind
BEGIN/END sentinels instead of being write-once, and the first version's
sentinel-less block is stripped on sight so no page keeps two copies.
Verified: 390 px — p scrollWidth == clientWidth == 320, document overflow 0,
URL wraps; newest entry lists c759c50 and this commit; island 83 entries,
14 commit chips matching 14 shown items; CHANGELOG.md byte-identical on a
second run.
| -rw-r--r-- | CHANGELOG.md | 65 | ||||
| -rw-r--r-- | scripts/build-changelog.py | 35 |
2 files changed, 61 insertions, 39 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 26ce257..3bfb483 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,16 +4,11 @@ Generated from git history by `scripts/build-changelog.py` — do not edit by ha <!-- generated:start --> -## v4.6.146 — 2026-11-05 +## v4.6.146 — 2026-11-06 -_Touches docs (11), modules (3), README.md (3), main.html (1) · docs 10, release 3, project 1_ +_Touches docs (11), modules (3), README.md (3), CHANGELOG.md (2) · docs 10, fix 3, release 3_ -- **One dashboard — port billing/API keys, 301 the duplicate** `8bf70f9` - - Two dashboards existed and had drifted apart: root /dashboard.html (118 KB single file with Subscription & Usage + API Keys) and /spaces/dashboard.html (34 KB shell + assets/js/dashboard-*.js). The nginx log settles which one is real: 77.211.5.129 requests /spaces/dashboard.html and never the root page. Tonight's fixes landed on the root page twice before that was noticed. - - Neither page was a superset, so deleting outright would have lost features: root only -> Subscription & Usage card, API Keys card (create/revoke/reveal) spaces only -> 2FA/TOTP, change password, delete space, email change, files, logs - - … run `git show <sha>` for the rest -- **Publishable README, MIT licence, security policy** `447be4b` -- **Clone instructions in the README; publisher sets gitweb owner/description** `350e1d0` +- …and 3 earlier commit(s) in this build - **Stop prepending the preamble on every run** `5902d5f` - write_md() put the H1 + description inside the generated body and also kept old[:m.start()], so each regeneration added another copy: 44 runs left 44 H1s and pushed the first release to line 179. Only the region between the markers is generated now, an existing file with N copies is collapsed to one, and an assert refuses to write a file containing two generated regions (proven RED by injecting a duplicate). Regenerated: 83 entries, 185 commits, first heading v4.6.146 (was v4.6.135 - the file had not been committed since 2026-11-02). - **Per-OS clone guide (Linux, macOS, Windows, Termux, iOS)** `8e34256` @@ -37,7 +32,19 @@ _Touches docs (11), modules (3), README.md (3), main.html (1) · docs 10, releas - **Namespace contrib refs per account (contrib/<user>/<topic>)** `4acb065` - Done before anyone has a token or a branch, because renaming later breaks every contributor. The hook compares the middle path segment to REMOTE_USER, so a token can only write under its own namespace; deletion and cleanup become a prefix operation instead of a guess. - Verified 4/4: own namespace accepted, neighbour's namespace refused naming the authenticated user, missing owner segment refused with the correct form, main refused. Test branch and token removed afterwards; queue back to 0. -- …and 1 more commit(s) in this build +- **Self-service git push tokens from the dashboard** `5b55af9` + - GET/POST /api/auth/gitpush-token (+ /revoke) on auth-api: session-scoped, CSRF-gated, 3 mints per account per hour, bcrypt into the htpasswd, plaintext returned once and never stored, audit at /var/lib/webx11/git-push-audit.jsonl. + - No sudo or reload needed: nginx re-opens auth_basic_user_file per request, so a new line is live immediately — proved by a 500 whose error line was per request. + - Dashboard card 'Git push token' shows none/active, mints and reveals the remote once, revokes. Verified in a real browser: badge none -> mint -> active, remote shape checked, then revoked; htpasswd back to 0 bytes and the test session removed. +- **Remove published dashboard fixtures from the space** `0fe3af9` + - Symptom: an outside review of the public mirror reported trade-history.json and wallet-snapshot.json at repo root as personal financial records, contradicting README's promise that personal data is absent by design. + - Cause: they are fixtures, not records (trade-history.json:60 "Sample data for dashboard rendering", wallet-snapshot.json:13 source=manual-baseline, address 0xA12b…3d4 synthetic), committed by an auto-commit in July and left at the space root. Nothing in the tree ever referenced either path (grepped the whole web root, 0 hits), and both answered HTTP 200 unauthenticated at /spaces/newos4/<file>.json — so a reader could only take them at face value. + - Fix: deleted both files, and admin/scripts/publish-newos4.py now lists them in EXCLUDE_GLOBS, which strips them from the mirror's rewritten history too (a tip deletion alone would leave them in every earlier commit). + - … run `git show <sha>` for the rest +- **Render the commit body, not just the subject** `c759c50` + - Symptom: the mirrored CHANGELOG.md (and the page) showed one-line commit summaries while README promises "every shipped fix, symptom -> cause -> fix -> verified". A reviewer measured the gap from the public mirror and could only conclude the claim overpromised. + - Cause: scripts/build-changelog.py:59 formatted commits with %s only; %b was never read, so the verification prose every fix commit carries (5b55af9 is a typical one) was discarded before rendering. The MD writer then replaced every tag with a backtick, so nothing richer could have survived anyway. + - … run `git show <sha>` for the rest ## v4.6.145 — 2026-11-02 @@ -1063,10 +1070,7 @@ _Touches modules (2), main.html (1), scripts (1) · project 1_ _Touches modules (55), skills (36), downloads (32), data (11) · project 11, fix 4, release 2_ -- **2026-07-09_06:00 changes** `97db1b5` -- **2026-07-29_20:50 changes — gunes sponsorluk page, bunq payment modal, AI platforms, news W30** `8dbbab4` -- **Add kanban module to newOS4 — todo/doing/done board with priorities** `4a3012a` -- **Register kanban in module-loader, bump to v4.6.11** `aee87f5` +- …and 4 earlier commit(s) in this build - **Add kanban to packages.json, update cache version to 2026-07-29** `c1b96f7` - **2026-07-29_20:45 changes** `79f682c` - **Tail -n/-N flags + live follow mode (-f) with test harness** `1692f20` @@ -1077,26 +1081,29 @@ _Touches modules (55), skills (36), downloads (32), data (11) · project 11, fix - **Typo suggestion bar (one-tap fix), consistent unknown-cmd messages, AI loop guard** `2cf0d3d` - **Race-safe AI loop guard (depth counter), cover _capture* entry points, structured JSON tool error** `8f2fb31` - **Parse per-call captured output for unknown-cmd error (race-free under Promise.all)** `212c05d` -- …and 4 more commit(s) in this build +- **Regex captures full command name + suggestion in unknown-cmd tool error** `00a6f24` +- **Reentrant print-capture stack, AI tail -f block, read-only sed in safeRead, security test harness (21 checks)** `4e57424` +- **Sequential safe-tool execution — parallel fan-out corrupted print-capture attribution + shadow diff pairing** `0c5316e` +- **2026-07-29_20:45 changes** `b1fb657` ## before v4.6.10 — 2026-07-02 _Touches modules (317), skills (161), backups (49), downloads (40) · project 47_ -- **2026-05-25_18:02 changes** `aa7bb1a` -- **2026-05-26_06:00 changes** `f37a1a8` -- **2026-05-27_06:00 changes** `d632dbc` -- **2026-05-28_06:02 changes** `c8c4089` -- **2026-05-28_18:00 changes** `0407f24` -- **2026-05-29_06:01 changes** `685a33d` -- **2026-05-29_18:01 changes** `2fb6b53` -- **2026-05-30_06:00 changes** `79e9df7` -- **2026-05-30_18:00 changes** `69d6d95` -- **2026-05-31_06:00 changes** `5128c54` -- **2026-06-01_06:00 changes** `c9a346a` -- **2026-06-01_18:00 changes** `ac50dcf` -- **2026-06-02_06:00 changes** `b086c2b` -- **2026-06-02_18:00 changes** `6ad20b8` -- …and 33 more commit(s) in this build +- …and 33 earlier commit(s) in this build +- **2026-06-15_06:00 changes** `1e964c9` +- **2026-06-15_18:00 changes** `a0afeba` +- **2026-06-16_06:00 changes** `bd9db0e` +- **2026-06-17_06:01 changes** `75e5278` +- **2026-06-17_18:01 changes** `9f82275` +- **2026-06-18_06:00 changes** `b348bd5` +- **2026-06-18_18:00 changes** `7f5129f` +- **2026-06-19_18:01 changes** `c83f38a` +- **2026-06-22_18:00 changes** `536c2ad` +- **2026-06-23_06:00 changes** `e50d590` +- **2026-06-23_18:00 changes** `bdab7cb` +- **2026-06-24_06:01 changes** `c2c71ba` +- **2026-06-25_06:00 changes** `e683a33` +- **2026-07-02_06:00 changes** `ae177bd` <!-- generated:end --> diff --git a/scripts/build-changelog.py b/scripts/build-changelog.py index 29d8e83..391581b 100644 --- a/scripts/build-changelog.py +++ b/scripts/build-changelog.py @@ -268,12 +268,16 @@ def build(): entries = [] for ver, group in releases.items(): items = [] - for row in group[:MAX_ITEMS]: + # The NEWEST commits of a build, not the oldest: group is chronological, so group[:N] + # showed the beginning of a 17-commit build and dropped the fixes that shipped last — + # measured 2026-11-06 when this fix's own commit was cut off its own entry. + shown = group[-MAX_ITEMS:] + if len(group) > MAX_ITEMS: + items.append('…and %d earlier commit(s) in this build' % (len(group) - MAX_ITEMS)) + for row in shown: items.append('<strong>%s</strong> <code>%s</code>%s' % ( clean(row['subject']), html.escape(row['sha'][:7]), detail_html(detail_blocks(row.get('body'))))) - if len(group) > MAX_ITEMS: - items.append('…and %d more commit(s) in this build' % (len(group) - MAX_ITEMS)) kinds = Counter(kind_of(r['subject']) for r in group) # The dominant kind makes the chip honest: a build that was 9 fixes and 1 # feature is a fix build, not a feature release. @@ -290,7 +294,7 @@ def build(): 'note': 'Touches ' + ', '.join('%s (%d)' % (a, n) for a, n in areas.most_common(4)) + ' · ' + ', '.join('%s %d' % (k, n) for k, n in kinds.most_common(3)), 'sources': sorted({f for r in group for f in r['files'][:1]})[:4], - 'commits': [r['sha'][:7] for r in group[:MAX_ITEMS]], + 'commits': [r['sha'][:7] for r in shown], }) entries.reverse() # newest build first @@ -364,16 +368,22 @@ def existing_island(): # Rendered as native <details> inside each list item, so the page's own JS needs no change. # Injected once (sentinel-guarded) because the page is a static document: a builder that only # rewrites its JSON island cannot restyle anything else. -DETAIL_CSS = """ /* wx11-changelog-detail: collapsible commit body */ +DETAIL_CSS = """ /* wx11-changelog-detail:BEGIN */ .entry li>details{margin:.3rem 0 0} .entry li>details>summary{ - cursor:pointer; font-family:var(--mono); font-size:.66rem; letter-spacing:.08em; - text-transform:uppercase; color:var(--ink-3); padding:.15rem 0; + cursor:pointer; font-family:var(--mono); font-size:.7rem; letter-spacing:.08em; + text-transform:uppercase; color:var(--ink-3); padding:.45rem 0; } .entry li>details>summary:hover{color:var(--cyan)} - .entry li>details p{margin:.35rem 0 0; font-size:.88rem; color:var(--ink-2); line-height:1.55} - .entry li>details ul{margin:.3rem 0 0; padding-left:1rem; font-size:.88rem; color:var(--ink-2); line-height:1.55} + .entry li>details p{margin:.35rem 0 0; font-size:.88rem; color:var(--ink-2); line-height:1.55; overflow-wrap:anywhere} + .entry li>details ul{margin:.3rem 0 0; padding-left:1rem; font-size:.88rem; color:var(--ink-2); line-height:1.55; overflow-wrap:anywhere} + /* wx11-changelog-detail:END */ """ +DETAIL_CSS_RX = re.compile(r'/\* wx11-changelog-detail:BEGIN \*/[\s\S]*?/\* wx11-changelog-detail:END \*/\n?') +# The first version shipped a single-line sentinel and no end marker; strip it or the page +# would keep both copies forever. +LEGACY_CSS_RX = re.compile(r'\s*/\* wx11-changelog-detail: collapsible commit body \*/' + r'[\s\S]*?\.entry li>details ul\{[^}]*\}\n') def write_page(page): @@ -390,7 +400,12 @@ def write_page(page): new = re.sub(r'(<section class="panel" aria-labelledby="gaps-h">[\s\S]*?<h2 id="gaps-h">[^<]*</h2>\s*<p>)[\s\S]*?(</p>)', lambda m: m.group(1) + GAP_INTRO + m.group(2), new, count=1) new = re.sub(r'(<b id="cur-ver">)[^<]*(</b>)', lambda m: m.group(1) + html.escape(page['currentVersion']) + m.group(2), new, count=1) - if '/* wx11-changelog-detail' not in new: + new = LEGACY_CSS_RX.sub('\n', new, count=1) + if DETAIL_CSS_RX.search(new): + # Owned block: re-emitted every run, so the rules can be edited here (the page is + # static; a stale copy on disk would silently win). + new = DETAIL_CSS_RX.sub(lambda m: DETAIL_CSS, new, count=1) + else: new, n = re.subn(r'(\n\s*</style>)', lambda m: '\n' + DETAIL_CSS + m.group(1), new, count=1) assert n == 1, 'no </style> to inject the detail CSS before - page layout changed' write_atomic(PAGE, new) |