Skip to content

Release 1.20.0 → PROD - #2058

Merged
jung-thomas merged 54 commits into
mainfrom
DEV
Aug 27, 2026
Merged

Release 1.20.0 → PROD#2058
jung-thomas merged 54 commits into
mainfrom
DEV

Conversation

@jung-thomas

Copy link
Copy Markdown
Contributor

DEV → main promotion for the 1.20.0 PROD deploy. 52 commits ahead of main.

Version

  • .deploy/mta.yaml 1.19.0 → 1.20.0 (minor — feature release).

Highlights since 1.19.0

Verification

  • Deployed + content-published to DEV from this tip (v1.20.0 · gitSha 27453d1): /version OK, content publish Verify OK: 715 slugs match, "Tag the Cat" confirmed live in the shipped island bundle, /whats-new/ serving the new digest.
  • Smoke: the only failures are the known-benign advocates/browse <main><div> (a11y: duplicate-main + browse banner + figcaption contrast + concepts landmark #1989) stale-test assertions + the data-gated concept community-events check.

Merge + deploy

  • Merge with a MERGE COMMIT (not squash), per the DEV/main branching model.
  • After merge: PROD deploy from a FRESH origin/main (primary tree), blue-green; then the two PROD content-rebuild workflows.

The freshness scan judged code blocks in isolation and applied generic
best-practice dogma, producing false positives (intentional errors,
demo base64 credentials, dev-container setup, npm version-pinning advice
that contradicts CAP guidance).

- extractCodeBlocks now captures contextBefore/contextAfter (the prose
  paragraph adjacent to each fence).
- extractTutorialContext pulls frontmatter + Prerequisites, fed once at
  the top of the prompt so the model knows the reader's environment.
- SYSTEM_PROMPT now instructs the model to judge blocks in context,
  respect intentional teaching artifacts (deliberate errors, illustrative
  demo credentials), and follow SAP/CAP conventions (never suggest pinning
  @sap/* package versions).
- Export SYSTEM_PROMPT/buildUserMessage; add extractor + prompt tests.
- PRECISION: prefer no finding over a speculative one; report each issue once.
- OUTPUT vs CODE: never flag staleness/secrets inside illustrative output blocks.
- SEVERITY defined by reader impact (fails today / deprecated path / cosmetic).
- SCOPE: skip prose, screenshots, product-name currency, deliberate simplifications.
- GROUNDING: quote the offending token; flag training-data inferences as Low.

Reframes the reviewer as helping the tutorial AUTHOR. Adds prompt-guard tests.
…t-fix

freshness: feed tutorial context + SAP-aware rules to the scan
…ckfiles

setup-node's 'cache: npm' already warms ~/.npm, but 'npm ci' still spends
~48s extracting/linking 1680 packages + native builds into node_modules on
every rebuild run (verified in run 32783495862: cache restored, yet 'added
1680 packages in 48s'). Cache the materialized node_modules (root +
hugo-apps) on an exact lockfile key and skip the install on hit.

Exact-key only (no restore-keys): skipping 'npm ci' means a mismatched tree
would go unreconciled, so any lockfile change must miss and reinstall clean.
Safe because neither package.json has install/prepare/postinstall lifecycle
scripts -- nothing outside node_modules is produced at install time.
…o-end)

Design + specs + tasks for making a single-tutorial rebuild scale with
changed slugs instead of the full corpus. Four independently-shippable
workstreams, grounded in code research (file:line):

- content-delta-publish: mutable ContentCurrent + append-only ContentHistory
  replaces full-snapshot-per-version + carryForwardUnchanged (~95s server cost)
- generated-content-cache: cache hugo/content/tutorials (~25MB) with a
  correctness-first key (hashes /build feeds + parser source + per-slug source)
- single-slug-render: scoped Hugo render (R2 verified coherent)
- tutorial-discovery: unmask GraphQL errors + fix App-token org-node auth +
  loud REST fallback

Companion CI win (node_modules cache) shipped separately in #2016.
perf(rebuild): cache node_modules to skip ~48s npm ci on unchanged lockfiles
…a-rebuild

Design: slug-targeted delta rebuild (O(changed) end-to-end)
graphqlRequest warn-logged a GraphQL errors/null-data response and returned
the null data, so callers dereferenced data.organization/data.repository and
threw an opaque TypeError — which the outer catch mistook for an outage and
SILENTLY degraded discovery to the slow per-slug REST path (~1400 extra calls,
secondary-rate-limit risk) every rebuild.

- graphqlRequest now throws a typed GraphqlError carrying the GraphQL error
  type+message; throws on errors[] or null data.
- GraphqlError.isAuthError classifies FORBIDDEN/INSUFFICIENT_SCOPES/permission
  failures (the App-installation-token org-node gap).
- discovery + metadata-batch fallbacks log at ERROR level (once) when the cause
  is an auth/permission error, so a token-scope regression is visible instead
  of hidden as latency.

Workstream A of the slug-targeted-delta-rebuild design (#2017). The auth fix
itself (repo-oriented discovery vs PAT vs App Org:Read) is Workstream B and
gated on the real error this now surfaces on DEV.

Test: test/unit/github-graphql-error-unmask.test.ts (6 cases).
fix(fetch): unmask GraphQL discovery errors + loud REST fallback
Pure, tested helpers for the slug-targeted fast path: feed fingerprint
(catalog/co-completions/tag-labels), sidecar read/write, fast-path
eligibility decision, and navEntries-by-slug reconstruction. Fail-open
throughout. Nothing calls these yet — the fetch-tutorials.ts reuse wiring
(flag-gated, requires a byte-identical DEV diff-verify) + the actions/cache
workflow step land next.

Test: test/unit/content-cache.test.ts (15 cases).
feat(rebuild): generated-content cache — Workstream C (foundation, draft)
…ated off)

On a slug-targeted run with CONTENT_CACHE_FAST_PATH=true, fetch-tutorials
reuses the previously-generated .md + sidecar nav/author rows for non-target
slugs, skipping composeTutorial/fetchRulesVr/AI-quiz/writeHugoPage — the bulk
of Phase 3's ~56s. Two gates, both required, else full regen:
  - actions/cache KEY = parser-source hash (a parser change → cache miss → the
    per-slug existsSync guard falls through to recompose)
  - runtime feed fingerprint over CAP catalog + tag-labels (nav/mission/tags);
    co-completions excluded (empty on warm-cache runs, recommendations are
    client-hydrated)
Sidecar (navEntries + authorRows + fingerprint) written to .content-cache/,
cached by the same parser-hashed key as hugo/content/tutorials +
image_dimensions.json. Fail-open throughout; flag defaults OFF so default
behavior is byte-identical to today.

Enabling on DEV (input content-cache=true) + a byte-identical diff-verify
(task 3.6) is the follow-up before flipping the default.

Helper unchanged behavior; content-cache tests green (15).
…n B schema, Workstream D)

Mutable current-content table (one row per slug, no version column) + append-only
per-(version,slug) history carrying the BLOB for self-contained rollback replay
(design.md D1/D2). Mirrored into the QA namespace. Verified: full model compiles +
deploys to sqlite::memory:.

NEXT (gated, not in this commit): .hdbmigrationtable staging for the two new HANA
tables (db/src/ + db/last-dev/ regen via the project's cds-build-staging procedure)
+ hybrid deploy verify — the version-counter-hazard step. Then write path (dual-write,
flag), readers, rollback.
… (Workstream D, flag-gated)

commitSession now mirrors freshly-published slugs into the mutable ContentCurrent
(UPSERT, one row per slug) + append-only ContentHistory (WRITTEN rows) when
CONTENT_DELTA_WRITE_ENABLED=true, alongside the legacy ContentFiles write.
Chunked BLOB read mirrors carryForwardUnchanged (raw db.run on HANA for the
LOB-locator path, CQL on SQLite). Fail-SAFE: never throws into the commit tx —
legacy ContentFiles stays the source of truth until the reader cutover.

Tests (SQLite): test/unit/content-delta-dualwrite.test.js (4) — one-row-per-slug,
UPSERT on republish, history append per version, flag-off writes neither.
Existing commit-path tests still green (carryforward-memory + guard, 12).
HANA LOB path is covered by the hybrid publish→rollback test (task 7.4).
feat(rebuild): generated-content fast path — Workstream C wiring (flag-gated, draft)
…h-schema

feat(db): Option B schema — ContentCurrent + ContentHistory (Workstream D, draft)
rules.vr answer options like [ ] no / [x] yes rendered as false/true (and
a yes/no correctAnswer flipped to a boolean) because the frontmatter
serializer emits bare yes/no/on/off tokens under YAML 1.2, which Hugo's
YAML 1.1 parser then reads as booleans. jsonify ships those booleans into
<script id="tutorial-data"> and the quiz island renders "false"/"true".

Add scripts/lib/hugo-yaml.ts (hugoFrontmatterStringify, version: '1.1') so
the writer quotes every token a 1.1 reader would misparse, and route all
Hugo-frontmatter serializers through it (render-frontmatter, fetch-tutorials,
fetch-advocates, author-pages-writer). Regression test round-trips through a
YAML 1.1 read.
fix(#2023): stop Hugo coercing yes/no quiz options to true/false
The canonical notify-workflow template (docs/authors/tutorial-repo-dispatch.yml)
triggered only on `branches: [main]`. Source repos are a MIXTURE of default
branches — most default to `main`, but some (e.g. sap-tutorials/Tutorials)
default to `master`. On a master-default repo a main-only trigger never fires,
so edits never dispatch `tutorial-updated` and PROD silently goes stale
(root cause of the cp-* content-drift incidents; Tutorials was hand-patched by
sap-tutorials/Tutorials#24184 but the template itself was never updated — so a
future `install-notify-workflows` run would REVERT that fix).

- Template now triggers on `[master, main]` (GitHub ignores a listed branch
  that doesn't exist, so it's safe on every repo regardless of default).
- Add a regression guard in the installer unit test asserting the template
  fires on both branches and never ships a main-only trigger.

Propagate to source repos with `npm run install-notify-workflows -- --execute`.
Pre-existing DEV breakage inherited by this PR's CI merge ref: the
feature-flag registry drift guard (test/unit/feature-flags-registry.test.js)
fails because srv/lib/content-publish-session.js reads
process.env.CONTENT_DELTA_WRITE_ENABLED (Workstream D Option-B dual-write)
without a matching FEATURE_FLAGS entry. Register it (env, true-enables,
default OFF, dev-only) so the guard passes.
fix(notify): dispatch source-repo rebuilds on both master and main
…lag-gated, draft) (#2028)

* feat(serve): Option B read cutover for serveStoredSlug (Workstream D, flag-gated)

serveStoredSlug serves from the mutable ContentCurrent (WHERE slug=?, no
version join) when CONTENT_DELTA_READ_ENABLED=true AND the slug is present,
else falls back to the legacy version-pinned ContentFiles snapshot. Per-slug
fallback keeps a partially-populated ContentCurrent (pre-full-seed) from
404-ing slugs still in ContentFiles. LOB BLOB read stays on raw db.run for
HANA (new hanaCurrentTableName helper). X-Content-Source: db-current | db.

Covers the hot path + pages/author/advocate (all route through serveStoredSlug).
Special-slug/catalog/embedding readers still use ContentFiles (follow-up 6.2-6.5)
— consistent because ContentFiles stays fully populated + authoritative.

Tests: test/unit/content-delta-read.test.js (3); content-store legacy suite green (48).

* feat(rollback): Option B ContentCurrent clear on rollback (Workstream D, flag-gated)

During the dual-write migration window, rollbackHandler now clears ContentCurrent
after the manifest flip (when CONTENT_DELTA_WRITE_ENABLED=true) so every read
falls back to the just-restored authoritative ContentFiles(target.version) —
correct-by-fallback, no per-slug BLOB replay needed. ContentHistory is retained
(append-only) for the post-ContentFiles-retirement full history-replay (task 8.4).
Fail-safe: never breaks the legacy rollback.

Test: content-delta-read.test.js rollback case (publish v1+v2 → rollback v1 →
ContentCurrent cleared → serve falls back to ContentFiles D1). 4 tests total green.
…2030) (#2031)

Add query-string state sync to the Devtoberfest sessions grid so search,
week/track filters, edition, and an open session panel are all shareable
and restorable from the URL:

  /devtoberfest/sessions/?q=<text>&week=<w>&track=<name>&edition=<id>&session=<id>

- New pure url-state.ts (parse/serialize + validation), mirroring the
  calendar's #2006 url-state.ts and the concepts-filter precedent;
  fail-open (empty/unknown params -> null, never throws). Unit-tested
  (round-trip, empty/whitespace rejection, unknown-param drop).
- App.vue: parse on mount (q/week/track applied synchronously; session
  applied once the feed loads), history.replaceState on state change,
  popstate re-sync. App-level integration tests cover restore-from-URL,
  session-panel open, write-back, and unknown-session fail-open.

Closes #2030
… (Workstream D 6.2-6.5, 8.4) (#2032)

* feat(serve): migrate __404__ + __nav__ single-slug readers to ContentCurrent (Workstream D 6.2)

Adds shared resolveContentBlob helper (ContentCurrent-first when read flag on +
present, else legacy ContentFiles active snapshot; raw db.run LOB on HANA).
serveNotFound (__404__) + navHandler (__nav__) now route through it — same
per-slug fallback as serveStoredSlug, safe on a partially-seeded ContentCurrent.

Enumeration readers (nav-fallback/hashes/source-hashes/embeddings) deliberately
NOT migrated yet — they enumerate ALL slugs and must wait for the full
ContentCurrent seed (task 4.3), else they'd return only the dual-written subset.

Tests: content-store (48) + content-delta-read (4) green.

* feat(serve): migrate hashesHandler + navHandlerFallback to ContentCurrent (Workstream D 6.3)

Both enumerate the active slug set (metadata-only, no BLOB) — switch to
ContentCurrent (no version) when CONTENT_DELTA_READ_ENABLED, else legacy
ContentFiles active snapshot. Correct once ContentCurrent is fully seeded
(task 4.3, running now via a full force rebuild). Tests: 52 green.

* feat: complete Option B reader sweep — all readers off ContentFiles-version (Workstream D 6.2-6.5)

Migrates the remaining readers to ContentCurrent (read-flag gated, per-slug or
enumeration fallback to legacy ContentFiles):
- chrome-shell __shell__ (ContentCurrent-first + version fallback; version cache
  key kept — self-invalidates as manifest version still bumps per publish)
- sourceHashesHandler, getTutorialSource (source-column reads)
- embedding readContentBuffer + active-slug-set reads (embedding-stats,
  embedding-reconciliation, cleanup.pruneOrphanEmbeddings, admin seedEmbeddings)

No cache re-key needed (6.5): version-keyed caches invalidate on each publish's
manifest-version bump regardless of read source. With ContentCurrent seeded
(2362 slugs), every reader now serves from it under the read flag. This is the
prerequisite for removing carryForwardUnchanged (8.4).

Tests: content-store + delta-read + dualwrite + carryforward (58) green.

* feat(publish): Option B carry-forward removal + rollback replay (Workstream D 8.4 — the payoff)

CONTENT_DELTA_SKIP_CARRYFORWARD (default off) makes publish O(changed slugs):
commitSession skips carryForwardUnchanged, so ContentFiles(newVersion) holds
only the freshly-published slugs. ContentCurrent (mutable, dual-written) stays
complete and serves everything; the ~95s PROD carry-forward disappears.

Coupled rollback rework: with carry-forward off, ContentFiles(V) is no longer a
complete snapshot, so rollbackHandler now REPLAYS ContentHistory into
ContentCurrent (per-slug latest version <= V, chunked LOB read) instead of
clear+fallback. detectReverts/history untouched.

Adds invalidateContentCache() export (prod busts via cache-generation token on
publish; ops/tests that drive publish directly use this).

Tests: content-delta-carryforward-skip.test.js (2) — O(changed) publish
(ContentFiles holds only changed slugs) + byte-correct multi-version rollback
replay. Full content suite 70 green.

Enable order (per env): read cutover deployed + ContentCurrent seeded, THEN
flip CONTENT_DELTA_SKIP_CARRYFORWARD.

* chore(feature-flags): register CONTENT_DELTA_READ_ENABLED + SKIP_CARRYFORWARD env flags

Adds the two remaining Option B env flags to the feature-flag registry
(CONTENT_DELTA_WRITE_ENABLED was already registered). Satisfies the
feature-flags-registry drift guard. dev-only status, default OFF.
The canonical .deploy/mta.yaml approuter builder copied admin-ui,
analytics-ui, explore-ui, scanner-ui and data-inspector-ui but never
built or copied app/display-app, so static/display-app/ never shipped
and the XSUAA-protected /display-app/ route 404'd in every env.

Root mta.yaml had the step but that file is not what mbt build uses.

Add npm install + vite build + copy of dist/ into static/display-app/,
mirroring the analytics-ui pattern (app has base '/display-app/').

Note: the per-app deploy guards (Step 3.5 admin bundle, 3.6 QA
navigator, explore-ui check) are hand-curated allowlists and none
covers display-app/scanner-ui — an omitted route slips through.
…icalized (#2036)

The Option B reader sweep (#2032) added new ContentCurrent read paths in
resolveContentBlob/serveStoredSlug (content-store.js) and readContentBuffer
(embedding-pipeline.js) mirroring existing ContentFiles reads, but only the
pre-existing lines carried the // slug-canonical: marker. The 8 new sibling
lookups use the identical caller-canonicalized slug (documented contracts),
so mark them consistently rather than adding redundant .toLowerCase().

Fixes check-slug-lookups build guard failure in CI.
Adds a search button (Ctrl+K / Cmd+K) in the admin ToolHeader that opens a
ResponsivePopover with a SearchField over a flat, filterable list of all admin
pages. Flattens the already role-filtered nav model so authors never see
admin-only pages. Navigation dispatch (keyed routes + special deep-link hashes
+ external href links) is refactored into a shared _navigateToNavItem helper
reused by the side nav and the palette.
…p) (#2039)

* docs(content-cache): follow-up plan to turn fast path ON by default (Workstream C 3.6/3.8)

* feat(content-cache): turn generated-content fast path ON by default

Workstream C follow-up (PR #2039). Implements the three items in the plan doc:

1. Byte-identical diff guard (task 3.6): test/unit/content-cache-diff-guard.test.ts
   exercises the REAL page generator (renderHugoFrontmatter) to prove the
   properties the fast path's byte-identity rests on — determinism, reused
   non-target page == full regen, non-target invariance to a target change, and
   that the feed-fingerprint gate is load-bearing. Network-free; runs every PR.

2. rebuild-content.yml: flip content-cache input default false->true (keep
   -f content-cache=false opt-out). FIX the repository_dispatch path — compute
   content_cache_enabled + fast_path in the 'Determine effective rebuild mode'
   step (was reading inputs.content-cache only, so dispatch always resolved
   false). CONTENT_CACHE_FAST_PATH env + the cache steps now use those outputs,
   so author hotfixes (repository_dispatch) get the fast path. Split the combined
   actions/cache into restore (slug-targeted only, keeps full rebuilds clean of
   orphan pages) + save (full AND slug-targeted, primes the sidecar). Summary
   telemetry line added.

3. rebuild-content-qa.yml (task 3.8): mirror — content-cache input (default ON),
   a 'Determine content-cache mode' step, and split restore/save cache steps with
   QA channel paths (hugo/content-qa, hugo/data-qa).

Refs slug-targeted-delta-rebuild 3.6/3.8.
The month view used grid-template-columns: repeat(7, 1fr), which resolves
to minmax(auto, 1fr). The auto min-track equals the widest nowrap chip, so
the five weekday columns blew far past the viewport (measured 3951px in a
1122px container) while Sat/Sun collapsed to ~30px and got clipped by the
grid's overflow:hidden — you saw ~2 columns and had to zoom out until it was
unreadable.

- Use repeat(7, minmax(0, 1fr)) + min-width:0 on cells/chips so the seven
  columns stay equal and the grid never exceeds its container.
- Compact each chip to viewer-local time + truncated title
  (formatViewerTimeShort); speakers move to the hover tooltip instead of
  eating grid width.
- Add an in-place per-day overflow popover: '+N more' opens a dialog listing
  ALL that day's sessions (closable via backdrop or Escape) instead of
  navigating away; the day number still opens the full Day view.
- Apply the same minmax(0,1fr)+min-width:0 hardening to WeekAgenda, which
  had the identical latent overflow.

Tests: new MonthGrid.test.ts (compact chips + popover), formatViewerTimeShort
unit tests, and updated the #2007 speakers guard for the month compaction.
… fixes (#2042)

Adds a Chrome-dino-style Easter egg to the /devtoberfest/ home page: the cat
(new Kasimir_Types.png) wanders a bounded arena; click/tap plays a meow and,
for signed-in players during the active event, awards 5 points/day (once/day,
capped at 100/event).

Frontend (devtoberfest island):
- CatGame.vue rendered below the info; Web Audio meow, wander/hit animation,
  prefers-reduced-motion + a11y (focusable button, aria-live), score HUD.
- Header cat image swapped to kasimir-types.png; new data-attrs wired through
  main.ts/types.ts/list.html. Award via shared csrfFetch (JSON POST).

Backend:
- CatGameAwards ledger (composite PK user/event/awardDate → DB-enforced
  once-per-day) + @cds.persistence.journal + generated migration table.
- POST /api/devtoberfest/cat-game/award (authenticated): active-event gating,
  once-per-day, 100-point cap; srv/lib/cat-game-award.js (pure, tested).
- Approuter route block (xsuaa) for the new endpoint.

Gameboard contract (fixes pre-existing gap flagged in #2042):
- GAMEBOARD_COMPLETION_V1 now surfaces PUZZLE + PETOBERFEST completions (was
  TUTORIAL-only), so those task types finally score on the leaderboard.
- New GAMEBOARD_BONUS_V1 (per-user cat-game points) + gameboard_reader grants.
  Consumed by the sibling gameboard-srv PR (per-user accrual added to score).

Tests: 9 backend (award logic + endpoint), 31 devtoberfest frontend. cds build
clean, zero migration/last-dev drift.
…vtoberfest Signups (#2047)

The Devtoberfest Signups Analytical List Page opened chart-first with the
X-axis bound to the internal integer weekIndex — meaningless to users. This
makes the report readable and adds the requested breakdowns:

- Readable week axis: add a real, GROUPABLE weekMonday:Date column so the
  chart groups on it and shows the week's Monday date instead of weekIndex.
  No portable date-add exists across HANA/SQLite, so weekMonday is supplied
  per dialect (db/sqlite/native.cds via strftime, db/hana/native.cds via
  ADD_DAYS) — the CAP-documented per-DB model pattern. The analytics view is
  split into a JOIN-bearing facts view + a JOIN-free public projection because
  the compiler refuses to `extend` a view containing a JOIN.
- Overall total: @UI.KPI #totalSignups header card (SUM(signups), unfiltered);
  the AnalyticalTable also renders its grand-total row.
- Region breakdown: a "By Region" SelectionPresentationVariant (chart grouped
  by region); every GroupableProperty (region/role/edition) is also reachable
  via the chart's built-in dimension drill-down.

enrich now derives weekLabel/cumulative from the real weekMonday when present,
falling back to weekIndex — keeping weekIndex-grouped reads working.

Wiring notes:
- package.json requires.db.[development].model=db/sqlite, [hybrid]/[production]=db/hana.
- .cdsrc nodejs build task also lists db/hana so the precompiled gen/srv/csn.json
  bakes the HANA weekMonday (verified: gen csn carries the ADD_DAYS calc).
- Specifying requires.db.model defeats cds's compiled-model fast path, so a cold
  cds.connect.to('db') in dev/tests recompiles the model (~500ms). This is
  dev/test-only (prod uses the precompiled csn). It exposed a latent 30ms race in
  test/unit/rebuild-trigger.test.js, fixed there by mocking resolveTenantSettings
  so the dispatch unit does no real DB work.

Verified: cds build --production (HANA hdbview uses ADD_DAYS; runtime csn has
weekMonday), sqlite $apply groupby(weekMonday) returns readable dates, prod-profile
model resolution loads cleanly, unit suite 8990 passed / 0 failed.
…dar-month-grid

fix(devtoberfest-calendar): month grid fits at normal resolution (#2046)
…gnups-report

Devtoberfest Signups report: readable week axis, total KPI, region breakdown (#2047)
feat(devtoberfest): Hit the Cat mini-game + gameboard task-type/bonus fixes (#2042)
Mirror the existing /build/verb-definitions pattern:
- anonymous app.get('/build/tags') in srv/server.js reads name+label from
  com.sap.developers.ims.Tags ordered by name, 60s Cache-Control, returns
  { tags: [names], buildAt }, 500s on error
- scripts/fetch-tags.ts writes hugo/data/tags.json, warn-and-continue
- wire fetch-tags into build:all next to fetch-verb-definitions
- unit test asserts endpoint returns tags array from seeded rows
feat: add /build/tags tag-taxonomy feed for CI frontmatter validation
feat(devtoberfest): rename "Hit the Cat" → "Tag the Cat" (#2042)
…lay names

ims.Tags.name holds a human display label ("SAP HANA Cloud"), not the
frontmatter-ready category>value slug. The CI frontmatter-unknown-tag
rule matches against category>value keys (e.g. "software-product>sap-hana-cloud").

Derive the correct key by calling titlePathToMdFormat on each row's
titlePath — the same algorithm used by AuthorService/AdminService to
populate the Tags.mdFormat virtual. Sort and deduplicate the resulting
set before emitting.

Unit test updated to seed titlePath values and assert mdFormat output.
fix(build/tags): emit mdFormat (category>value) slugs instead of display names
… axis, hidden weekIndex, labeled total, region/edition/role breakdowns (#2047)

Follow-up to #2047. The deployed report was more confusing than before:
- weekIndex still leaked as a visible 'Week #' dimension (449/451 internal values)
- two competing week dimensions (weekMonday Date + weekIndex int)
- the raw Date chart axis rendered a mix of formatted dates and bare day-of-month
- the total wasn't clearly labeled

Changes:
- Add a per-dialect display column weekStartText (HANA TO_VARCHAR 'DY DD MON YYYY'
  → 'Mon 07 Sep 2026'; SQLite falls back to the ISO Monday date — no name
  formatter). The chart + table group and sort on the real Date weekMonday but
  DISPLAY weekStartText via #TextOnly text arrangement, so the axis is a readable
  category string, not a thinned time axis.
- Hide weekIndex entirely (@UI.Hidden, dropped @Analytics.Dimension + removed from
  GroupableProperties) — kept only to derive weekMonday. Kills the 449/451 leak and
  the duplicate week dimension.
- Keep the labeled 'Total Registrations' KPI header card; drop the redundant ISO
  'Week' table column so there is a single week column.
- Add By Edition + By Role SelectionPresentationVariants alongside By Region.
- Guard weekStartText on both SQLite (unit) and HANA (hybrid) suites.

Verified: cds build --production (HANA hdbview carries TO_VARCHAR weekStartText;
CSN has text arrangement, hidden weekIndex, 3 breakdown SPVs); 25/25 signup unit
tests green.
…eadability

fix(admin): readable Devtoberfest Signups report — week axis, labeled total, breakdowns (#2047)
- MTA version bump (minor: DEV carries new features since 1.19.0 —
  admin page-search palette, Tag the Cat mini-game, /build/tags feed,
  Devtoberfest deep-links/speakers, tutorial reading prefs, freshness detector).
- What's New: +87 entries (PRs merged 2026-08-11 -> 08-27), total 321.
chore(release): bump MTA 1.19.0 → 1.20.0 + refresh What's New digest
An author-disabled section wrapped in a multi-line HTML comment
(e.g. <!-- ## Prerequisites ... -->) was mis-parsed: extractSection
lifted the commented heading out as a real section (leaking its
closing --> into the field) and extractIntro stranded the opening
<!-- into the intro. That unterminated comment then swallowed every
step when Hugo rendered the page (root cause of the
codejam-events-process-1-bah blank-steps break).

Add fence-aware commentLineFlags(); mask multi-line-commented lines
before section detection in extractFrontmatter and drop them in
extractIntro. Single-line comments (<!-- description -->, image
directives) are untouched. Verified: codejam original composes to 6
steps with empty prereq + balanced intro via the parser alone; full
corpus genuinely-broken count 14 -> 13 (only codejam removed, no new
breakage); 186 existing parser tests + 5 new pass.
…rser

fix(parsers): comment-aware section/intro extraction
…s despite feed drift

QA rebuilds ran ~10min (vs prod ~90s) for a single-slug preview. Root
cause: the content-cache fast path (decideFastPath) disables on a feed
fingerprint mismatch (catalog/tag-labels), forcing a full regen of all
~2100 tutorials. QA rebuilds are infrequent while prod republishes on
every merge, so by each QA run the catalog has drifted and the
fingerprint almost always mismatches — chronically defeating the fast
path. (The slug-filter Phase-2 scoping already works on QA; the full
regen, not discovery, was the cost.)

Add decideFastPath({ allowFeedDrift }) and set it for channel === 'qa'
only. On QA slug-targeted runs the fast path now reuses cached
non-target pages despite feed drift (the target slug is always
regenerated). Trades slightly-stale nav/tags on OTHER preview pages
(corrected on the next full QA rebuild) for a ~90s targeted preview.
Prod keeps strict feed-freshness (allowFeedDrift defaults false).

Tests: +4 decideFastPath cases (allowFeedDrift eligible on drift; still
requires sidecar + slug-targeted). 18 content-cache tests pass.
…-drift

perf(qa): reuse cached non-target content on QA slug-targeted rebuilds despite feed drift
…rry-pick) for 1.20.0 release

Merges origin/main into DEV so the 1.20.0 DEV→main release PR (#2058) is
conflict-free. All 4 conflicts (.deploy/mta.yaml, scripts/fetch-tutorials.ts,
scripts/lib/content-cache.ts, test/unit/content-cache.test.ts) resolved to DEV:
DEV carries the canonical content-cache fast path (#2039 + #2057 QA follow-up)
that main only has via the #2044 cherry-pick; version stays 1.20.0.
Resolved tree is byte-identical to origin/DEV — pure reconcile, no content change.
chore: sync main into DEV (reconcile for 1.20.0 release)
@jung-thomas
jung-thomas merged commit 131e747 into main Aug 27, 2026
12 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