Skip to content

Release 1.43.0: DEV to main promotion - #3044

Merged
jung-thomas merged 200 commits into
mainfrom
DEV
Nov 6, 2026
Merged

jung-thomas merged 200 commits into
mainfrom
DEV

Conversation

@jung-thomas

Copy link
Copy Markdown
Contributor

Release 1.43.0 — DEV → main promotion

Promotes the accumulated DEV work to main for the production deploy of release 1.43.0.

⚠️ Merge order: land the release-prep PR #3043 into DEV first (version bump 1.42.0 → 1.43.0 + What's New refresh), so this promotion carries .deploy/mta.yaml at version: 1.43.0. Verify the diff below shows the bump before merging.

What's shipping

Tutorial authoring — directive grammar system (new generic directive grammar + callouts, code-block/{diff}, rich media + asciinema ::cast, synced OPTION tabs, ::include content reuse & smart references, UI instruction directives, per-step pacing + interactive checklists).

Features — admin-toggleable RSS transport (fixes Khoros community-feed outage); Kasimir Arcade attract-mode game for event App-Space; per-event completion-report skill; mission completion-path ordering.

Deploy reliability — /health/ready blue-green readiness gate; scheduler cds.flush() self-heal + singleton-outbox fix; draft-orphan cleanup ops script.

Full developer-facing list on /whats-new/ (refreshed in #3043).

⚠️ Deploy note

cds.fiori.draft_admin_integrity is OFF in this release (reverted after the 1.42.0 incident). No pre-deploy HANA cascade-constraint step required.

Post-merge

Merging to main does not redeploy CF — the MTA deploy is a separate manual step. Deploy from a fresh origin/main once this lands (confirm scope + cf target first).

Pre-merge checklist

jung-thomas and others added 30 commits November 3, 2026 00:57
…not ON (#2909/#2993)

The Top Gotchas entry described draft_admin_integrity as ON with DB-level
cascade delete. That's stale: #2909 disabled the flag for all profiles after it
caused the 2026-11-02 PROD outage (flag on + no cascade constraints → orphaned
drafts → un-editable missions), and #3003 removed the constraint migration.

Rewrite to reflect reality: flag OFF, pre-10.2 delete hooks in use, real table
names (ADMINSERVICE_*_DRAFTS), the orphan-sweep script, and that re-enabling
the dad strategy is blocked on the unresolved HDI make-graph problem (#2993).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…d-draft-integrity

docs(CLAUDE.md): correct draft_admin_integrity gotcha — flag is OFF now (#2909/#2993)
…urls (#757 regression)

fix-archcenter-urls.cjs called cds.entities() after cds.connect.to('db')
with no cds.load(), tripping the #757 guard and failing the unit suite.
Under 'cds bind --exec' this throws 'cds.entities is not a function' at
runtime. Prime via cds.linked(await cds.load('*')) before connect and
resolve the namespace off the linked model, mirroring backfill-categories.cjs.
…hcenter

fix(script): prime CDS model before cds.entities() in fix-archcenter-urls (#757 regression)
…ix nudge (#2998)

The concept re-render delta phase keyed its digest on *ConceptLinks tables
(incl. HelpDocConceptLinks) but not on HelpDocs itself. The archcenter <a href>
is read live from HelpDocs.url at publish time, so a bare HelpDocs.url/title
change never bumped the digest -> concept pages phase-skipped and the corrected
URL never re-baked into the served BLOB.

- Add a HelpDocs probe (max(modifiedAt)+count(*)) to computeConceptInputDigest.
- Retire the manual HelpDocConceptLinks 'digest nudge' workaround in
  scripts/fix-archcenter-urls.cjs — the HelpDocs.url UPDATE now bumps the digest
  directly via managed modifiedAt.
- Add a unit test proving a HelpDocs-only probe change moves the digest.
…elpdocs

fix(concepts): observe HelpDocs in concept-input-digest (#2998)
…torials (#3007)

Track 'average time to complete' summed every tutorial across all segments,
double-counting branching (fork / parallel-any) paths — a two-lane fork
reported the combined time of both branches.

Extract trackTotalTime into app-space-time.ts: linear segments sum as-is,
fork segments average their per-lane totals, parallel-any clusters average
their per-tutorial times (sum of optional paths / number of paths). Legacy
flat items[] payloads still sum.
…bined-branches test

Code-review follow-up: removing [key: string]: unknown from TimeItem makes
AppSpaceTrack cleanly assignable to TimeTrack (fixes a tsc TS2345 at the
trackTotalTime call site). Add a test covering multiple branch segments in
one track (linear + fork + parallel-any).
…ng-3007

fix(app-space): average branching-path time instead of summing all tutorials (#3007)
…ag (#3011)

community.sap.com's Cloudflare bot management now 403-challenges the Khoros
LiQL API path (/api/2.0/search) that the default 'khoros' transport relies on,
breaking community-blogs-fetch in PROD (all 3 sources errored, fetched=0) and
degrading homepage RSS silently. The raw /rss/ feeds remain open, and the curl
transport (TLS-fingerprint bypass) still reaches them.

Previously the only switch was the RSS_TRANSPORT env var, which reverts on any
redeploy that doesn't re-set it. Move the khoros-vs-curl choice into the
feature-flags registry as COMMUNITY_RSS_USE_CURL (kind:'db', ImsConfig key
flag.community.rss.useCurl, default true -> curl) so operators can flip it from
the Admin UI Feature Flag viewer and it survives deploys.

- New shared srv/lib/rss-transport-mode.js rssMode(): env RSS_TRANSPORT wins
  when set (operator override + test seam; 'fetch' stays test-only), else the
  DB flag. Replaces the two drifted copies in the fetchers.
- Covers both community-blogs-fetcher and homepage-rss-fetcher.
- Default curl because the khoros path is currently dead.

Verified in PROD: after cf set-env RSS_TRANSPORT curl + restart, the next fetch
ran fetched=60 inserted=10 updated=47 errored=0.

Refs #3011
…ansport-flag

feat(community): admin-toggleable RSS transport feature flag (fixes Khoros 403 outage) (#3011)
…ate-test determinism (#3009)

C1: Add player-driven movement in renderer.ts (ArrowRight/ArrowUp/Space keyboard;
    pointer-right-half=run, pointer-left-half=tap=jump). attach/detachPlayerInput
    called by setPlayerControl so listeners are always cleaned up in stop().
    Add [data-game-close] button + Escape key handler in AppSpaceGame.vue so the
    player can always exit the overlay (closeGame: hide overlay, game.stop(),
    restart idle timer, no URL change).

C2: Add appSpaceGameEnabled:Boolean to getAppSpaceProgress return type in CDS.
    Import isFlagEnabled from ./lib/feature-flags/db-flags.js in developer-service.js;
    read APP_SPACE_GAME_ENABLED flag (default OFF, ImsConfig-backed) inside a
    try/catch->false wrapper and return it on the payload. Remove the isGameEnabled()
    reachability probe from feature.ts; AppSpaceGame.vue gates on
    data.appSpaceGameEnabled===true instead. Eliminates extra heavy query.

M2: Add dispose() to GameHandle interface and implementation; call game?.dispose?.()
    in onUnmounted to free WebGL contexts + geometries/materials.

M3: Add TextureLoader onError callback for Kasimir texture (silent console.warn,
    no throw, scene already survives missing texture).

Tests: remove 3 isGameEnabled probe tests (function removed); add tests for
    flag-OFF gate, close button, and Escape key. 37/37 pass on Node 22.23.1.
…esign

feat: Kasimir Arcade attract-mode game for App-Space (#3009)
The GitHub Pages build (predocs:build -> check-docs-sidebar.cjs) fails
because docs/developers/operations/draft-integrity-reenable.md exists on
disk but is not registered in the VitePress sidebar. Add the entry.
fix(docs): register draft-integrity-reenable runbook in sidebar
…0px off-page)

The App-Space game island mounts into an empty, zero-height <div id="app-space-game">
placed after the grid in the Hugo layout. The canvas used position:relative and the
Demo trigger position:absolute, so both laid out in normal flow ~1740px down the page
(below the fold) while the attract overlay (position:fixed, 92% opaque) covered the
viewport. Net effect: the renderer drew correctly (1830 draw calls/s, valid level
model, healthy WebGL2 context, all verified live) but onto a canvas nobody could see,
and the manual Demo trigger was unreachable.

Fixes:
- Canvas to position:fixed, inset:0, 100vw/100vh so it anchors to the viewport over
  the overlay dim while attract mode is active (the actual "nothing renders" bug).
- Demo trigger to position:fixed bottom-right (was absolute, landing at y~1748).
- startAttract() awaits nextTick before game.start() so the fixed canvas is laid out
  at its real size before the initial resizeToCanvas() bakes the camera aspect
  (prevents a stretched first frame).
- Add a window resize listener (armed in start(), removed in stop()/dispose()) so the
  full-viewport canvas keeps a correct aspect when the window resizes mid-attract.

Verified live on DEV: applying the fixed canvas CSS made Kasimir, coins, pillars and
the track flag render immediately; Demo button confirmed visible bottom-right.

Note: the app-space-game unit suite was already red on origin/DEV (VTU "ref on hoisted
vnodes" happy-dom issue, unrelated to this change).
…een-canvas

fix(app-space-game): render Kasimir arcade on-screen (canvas was ~1740px off-page)
…ntry

- Add scripts/vendor-asciinema.mjs: ESM download script for asciinema-player
  v3.18.0 JS + CSS from GitHub releases; idempotent via versioned sentinel
  file (.asciinema-vendored-3.18.0); guarded against test import side-effects
  via process.argv[1] check
- Add scripts/__tests__/vendor-asciinema.test.mjs: offline unit tests using
  vi.stubGlobal + vi.spyOn(fs); two cases: non-ok fetch exits 1, sentinel
  present skips download; fixed vi.stubGlobal return-value quirk by capturing
  mock fn before passing to stubGlobal (documented in test comment)
- Update vitest.config.ts: extend unit include patterns from {js,ts} to
  {js,mjs,ts} so .mjs test files are picked up
- Update package.json: add vendor:asciinema script; prepend to build:apps;
  append to setup
- Update REUSE.toml: add Apache-2.0 annotation for asciinema-player.css
…sk 3)

Adds rewriteSnippetImages() to include.ts which rewrites relative image
paths inside snippet markdown to absolute GitHub raw URLs pointing to the
snippet's own repo/branch, not the including tutorial's.

- shared/<name> snippets → tutorial-snippets/main
- <repo>/_snippets/<name> snippets → <repo>/main
- Absolute URLs and parent-relative (../) paths are left untouched
- snippetRepoMap in ExpandDirectivesContext allows explicit override
…pipeline

- Imports fetchSnippets from github.ts in fetch-tutorials.ts
- Adds snippet pre-fetch block after help-titles (gated by SNIPPETS_ENABLED_REPOS)
- Passes real snippetMap to every composeTutorial() call (replaces new Map() stub)
- buildSnippetDeps now receives real snippetMap + disk-cached rawMd per slug
- Removes TODO comments for Task 6/8; all fetch-tutorials.ts stubs are now wired
- Adds backward-compat integration test (empty snippetMap = byte-identical runs)
…th include source context

When expandDirectives() throws inside a snippet's content, the error
message is now augmented with:
  "<original message> (included from <tutorialSlug>:<line> via <snippetKey>)"

Avoids double-wrapping when the message already contains "included from".

Also adds gate regression tests confirming:
- empty snippetMap → directive/include-not-found (behavior unchanged for non-opted-in repos)
- tutorials with no ::include are unaffected (no error, identical output)
… undefined local var

The token variable in main() scope was undefined at runtime (it's only defined
inside fetchMarkdown()). fetchSnippets() uses the token for GitHub auth headers,
so passing undefined would skip auth and fail on private repos' _snippets/ dirs.

Use process.env.GITHUB_TOKEN ?? process.env.TUTORIALS_GITHUB_TOKEN directly at
the call site (stored as snippetToken) to avoid the scope issue.
…ude regex

Three bugs found in code review:

1. CRITICAL: fetchSnippets() stored shared snippets as 'shared/cf-cli.md' but
   resolveSnippetKey() normalizes ::include[shared:cf-cli] to 'shared/cf-cli'
   (no extension). Every shared snippet include threw directive/include-not-found.
   Fix: strip .md from the key when building shared/ keys. Per-repo keys keep .md
   since ::include[step.md] labels include the extension and suffix-match works.

2. MEDIUM: Same mismatch caused buildSnippetDeps to emit 'shared/cf-cli' keys
   while the rebuild workflow would compare against 'tutorial-snippets/...cf-cli.md'
   paths — shared-snippet changes never triggered targeted rebuilds.
   Fix: same as above (key consistency).

3. LOW: INCLUDE_RE in buildSnippetDeps used /:?:include/ matching one-colon
   :include[x] (not valid directive syntax) and producing false-positive dep entries.
   Fix: /::include/ (require two colons, matching actual leaf directive syntax).

Also updates tests:
- github-snippets.test.ts: assert 'shared/cf-cli' (not 'shared/cf-cli.md')
- github-snippet-deps.test.ts: add comment clarifying key convention; add test
  confirming :include (single colon) is not matched
- include-images.test.ts: shared snippet test uses 'shared/setup' key + label
  (no .md extension), matching the actual fetchSnippets output convention
chore(tutorials): CLI-deprecation rules + promotion lint + OS-fallback doc fix + MCP slicer import fix
feat(code-block): {diff} fence attribute for unified-diff blocks (code-block Task 2.3)
feat(content-reuse): wire ::include snippet pre-fetch + dependency manifest (content-reuse Task 6/8)
…p-pattern summary, %-through, org-domain rollup, engagement callout
…arsers-bundle

fix(mcp-slicer): static-import renderDirectivesAsText — unblock DEV unit-tests bundle step
feat(rich-media): asciinema player for ::cast recordings (rich-media Task 4.1/4.2)
…report-skill

feat: event-app-space-report skill (per-event completion report)
1. hugo/layouts/shortcodes/tut-menu.html: `contains` is not a Hugo
   function; the operands are strings → use `strings.Contains`. The bad
   token broke the Hugo build (exit 1), cascading to 4 hugo-homepage-partials
   failures (#8c2028daa).
2. package.json prebuild:parsers-bundle: align esbuild banner with the
   aliased `createRequire as __cjsBundleRequire` convention already used by
   bundle-shared.cjs. The bare `createRequire` collided with esbuild-emitted
   createRequire imports (from directives/ui.ts + reference-links.ts),
   throwing SyntaxError at module-load for 5 srv-qa test files.
3. Update 3 stale test assertions to match the intentional callout refactor
   (#18e713de5, #71a8d70bc): alerts now render as <aside>/<details> via the
   tut-alert partial and the CSS sibling rule broadened to
   `~ :is(blockquote, aside, details)`. Tests, not source, were behind.
The full-tree safe-HTML lint flagged 11 occurrences in hugo/layouts:
- 2 real safeHTML pipes missing a security-reviewed: marker
  (render-codeblock.html diff-rejoin branch, option-tabs.html panel emit)
- 9 false positives: prose comments containing the literal "safeHTML"
  token, which the line-level regex matches

Add security-reviewed: justifications to the two real calls (both assemble
HTML only from Goldmark-escaped input + constant scaffolding) and reword the
prose comments to drop the literal token. No behavioral change.
…akages

fix(ci): repair 3 pre-existing DEV unit-test breakages
fix(hugo): satisfy check-hugo-safe-html gate (#797)
…eak test

Follow-up after the first round fixed B/C/E but left A and D red.

A (5 srv-qa suites, 'Cannot find module ./icon-allowlist.json'):
  directives/ui.ts and reference-links.ts loaded icon-allowlist.json /
  api-libs.json via createRequire('./x.json') at runtime, but neither
  parsers bundle (srv-qa/lib + srv/lib/_shared, both built from
  scripts/parsers/*) ships the JSON beside the .mjs, so the runtime require
  fails. Switch to static default JSON imports (tsconfig has
  resolveJsonModule) — esbuild/vite/tsx all inline the JSON into the bundle,
  removing the sidecar-file dependency for BOTH bundles and also removing the
  createRequire imports that caused the earlier banner collision.

D (option-tail-leak.test.ts, 'Hugo build failed exit 1'):
  render-blockquote.html now delegates  alerts to the tut-alert
  partial (#2595/#71a8d70bc), but the test's temp Hugo site never copied
  tut-alert.html, so  was unresolvable and the
  build died. Copy tut-alert.html into the temp layouts/partials/ dir.
  Verified by reproducing the real Hugo pipeline: build now exits 0 and
  renders exactly 2 <aside class="alert alert-tip">.
…-and-alert-partial

fix(ci): inline parser JSON assets + copy tut-alert partial in option-tail-leak test
Bumps .deploy/mta.yaml 1.42.0 -> 1.43.0 and refreshes hugo/data/whats_new.json
(+56 entries) for the PRs merged to DEV since 1.42.0.

Highlights since 1.42.0:
- Tutorial directive-grammar authoring system (directive grammar foundation,
  callout/code-block enhancements, rich media + asciinema ::cast, synced OPTION
  tabs, ::include content reuse + smart references, UI instruction directives,
  per-step time/optional + interactive checklists)
- Kasimir Arcade attract-mode game for the event App-Space page
- Admin-toggleable RSS transport feature flag (fixes Khoros community-feed outage)
- Scheduler + /health/ready deploy-reliability hardening (blue-green readiness
  gate, cds.flush() self-heal, singleton outbox row fix)
- draft_admin_integrity remains OFF (reverted after the 1.42.0 incident)
release 1.43.0 prep: version bump + whats-new refresh
@jung-thomas
jung-thomas merged commit 1250a22 into main Nov 6, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant