Skip to content

Release 1.21.0 → PROD - #2119

Merged
jung-thomas merged 94 commits into
mainfrom
DEV
Sep 3, 2026
Merged

Release 1.21.0 → PROD#2119
jung-thomas merged 94 commits into
mainfrom
DEV

Conversation

@jung-thomas

Copy link
Copy Markdown
Contributor

DEV → main promotion for the 1.21.0 PROD blue-green deploy.

⚠️ Merge order

This PR is a draft on purpose. Merge #2118 (chore/release-1.21.0 → DEV) FIRST so DEV carries the .deploy/mta.yaml 1.21.0 bump + refreshed What's New digest. Once #2118 lands in DEV, mark this ready and merge to promote. Merging this before #2118 would ship 1.20.1-versioned content and strand the bump off DEV.

Version

Highlights since 1.20.1

Deploy

Blue-green to PROD space (manual, from a fresh tree; PROD deploy order planner→tutorials-ims→gameboard). Content publish into PROD via the two-workflow path after swap.

…2081-2083

chore: sync main into DEV (reconcile hotfixes #2081 + #2083)
… gotchas.md

Cuts per-session CLAUDE.md load ~59% (36.5k->15.1k chars). Verbose gotcha
paragraphs moved verbatim into tutorials-ims-gotchas.md 'Top Gotchas - full
detail'; items with dedicated reference docs (cds-caching, hcql, cap-ai,
e2e-coverage) link straight to those. No content deleted.
docs: slim CLAUDE.md Top Gotchas (relocate detail to gotchas.md)
…sses

The relocated KG coverage-nudge gotcha carried a bare <mission> token. VitePress
compiles docs/**/*.md as Vue SFCs, so the unclosed angle-bracket parsed as an HTML
tag and failed 'Deploy Docs to GitHub Pages'. Harmless in CLAUDE.md (not VitePress-built).
…bracket

fix(docs): unbreak VitePress build — backtick bare <mission> in gotchas.md
…s (#WS2)

Root cause: db/last-dev/csn.json (beforeImage for cds compile.to.hana diff)
was updated in the prior commit, so cds build --production saw zero diff and
emitted no migration. Workaround: restored csn.json to HEAD~1 baseline, ran
cds build --production with the second hana/dest:db task temporarily removed
(cds-caching/db/statistics clobber hazard), copied gen/ result to db/src/,
restored .cdsrc.json, re-ran deploy to regenerate csn.json correctly.
The migration-clobber workaround (temporarily removing the 2nd hana build
task) emitted TutorialValidationRules at a different position in
db/last-dev/csn.json than the canonical two-task `cds build --production`
that CI's cds-build-staging check runs. Content is byte-identical (pure
reordering); regenerated via canonical build. Migration tables unchanged.
…ments

Admin OP Phase 1: fix empty Categories / Contributors / Validation facets
…— preserve query, cache-control, slug validation
…land + page-topics; topic-detail CSS

Fix 1 (IMPORTANT): Add loadTopicCorpus() to topics-query.js and make buildTopicDetailPayload accept an optional corpus arg. In publish-topics.js, call loadTopicCorpus once before the loop and pass corpus to each call — eliminates O(topics × full-corpus) redundant DB reads at publish time. Single-slug serve path (srv/server.js) is unchanged (2-arg call, corpus absent).

Fix 2 (MINOR): Remove orphaned topics-map Vue island. Hugo topics layouts were deleted in Task 11; no live mount points remain. Delete src/topics-map/{main.ts,App.vue,ClusterMap.vue,App.test.ts} and vite.config.ts registration/budget plugin; remove island_manifest.json entry (gitignored, regenerates on build); update smoke test marker for /topics/ from id="topics-map" to id="topics-tree-root".

Fix 3 (MINOR): Drop page-topics from IN_SCOPE_PAGES — /topics/ is now served by CAP dynamic topic-index route, not a static page blob. Update page-key-map unit test to assert null for /topics/.

Fix 4 (MINOR): Add CSS for topic-* structural classes emitted by srv/lib/topic-detail-render.js (topic-detail, topic-breadcrumb, topic-detail__header/title/facet, topic-tutorials/concepts/related sections and their items/links) to hugo/assets/css/topics.css. The existing topics-detail/* cluster-detail CSS used topics-* (plural) but the renderer emits topic-* (singular) — genuine styling gap.
feat(topics): tag-tree topics — CAP-served index + detail, concept enrichment, search fix
…son status prop

PR #2099 added a `"status": 301` property to the /search route in
xs-app.json. @sap/approuter v16 has no route-level redirect verb and rejects
the unknown property at boot (xs-app.json/routes/52/status: Additional
properties not allowed), crash-looping tutorials-dev-approuter (0/1).

Fix mirrors the established sitemap-index-redirect.js pattern:
- remove the invalid route from xs-app.json (the deeper /search/<path>
  srv-api proxy route is untouched)
- add approuter/lib/search-redirect.js: a 301 middleware in
  insertMiddleware.first, before the static/proxy handlers, that redirects
  the exact /search entry point to /tutorial-navigator/ preserving the query
- register it in server.js after sitemapIndexRedirectHandler

Verified against @sap/approuter's own validators.validateXsApp: origin/DEV
fails at routes/52/status; this commit PASSES. 10 new unit tests green.
…rect-middleware

fix(approuter): /search 301 via middleware — unblock DEV approuter boot
…low-up)

flattenTopicSlug only collapsed '--'->'-' and lowercased, leaving the
spaces, colons and slashes that real Tags.titlePath values carry. Every
generated topic slug therefore failed serveHandler's VALID_SLUG guard
(/^[a-z0-9][a-z0-9-]*/), so all 152 published topic-<slug> BLOBs 404'd
on /topics/<slug> while the tree/index still linked to them.

- Add slugifyTopic(): fold every non-[a-z0-9] run to a single hyphen, trim.
- flattenTopicSlug delegates to it (subsumes the old '--' collapse).
- buildTopicSlugMap slugifies the facet for collision qualification and
  never emits a leading-hyphen slug when a facetless titlePath collides.
- Unit tests assert real-world dirty titlePaths yield VALID_SLUG-safe slugs.

Requires a full content republish so stored topic-* keys match the new
slugs (render-topics renders server-side).
fix(topics): URL-safe topic slugs so /topics/<slug> serves (#2099 follow-up)
…nal assets (#2103)

Adds an admin-gated check that reconciles each planner session's
SCHEDULEDSTART against the actual scheduled start times on its external
assets and reports any that have drifted apart:

- YouTube livestream (videos?part=liveStreamingDetails -> scheduledStartTime)
- community.sap.com event (Khoros LiQL -> occasion_data.start_time)

Zoom is not checked: the planner carries no readable Zoom start time
(bare join link, empty CALENDARINVITE, free-text ZOOMINVITEDETAILS).

- srv/lib/devtoberfest-schedule-check.js: pure parsers + comparison +
  assembly, plus two fetchers behind an injectable fetchImpl seam.
  Fails soft everywhere: an unreadable leg reports 'unknown', never a
  spurious 'drift'. Zero cds/db imports.
- srv/routes/devtoberfest-schedule-check.js: GET /api/devtoberfest/schedule-check
  ?edition=<id>&tolerance=<min>, admin-gated (401/403), Cache-Control: no-store.
- 25 unit tests covering parsers, tolerance boundaries, statuses,
  summary counts, fetcher batching/fail-soft, orchestrator wiring.

Verified live against Devtoberfest 2026 (87 sessions): community leg
resolved 40 event times (39 exact-UTC match, 1 real drift on INT-43 --
planner 22:00Z vs community 07:30Z next day). YouTube leg is fail-soft
until the deployed srv resolves YOUTUBE_API_KEY from credstore.
…edule-check

feat(devtoberfest): schedule-consistency check across planner + external assets (#2103)
Publishes ACTIVE developer advocates as a cross-container value-help view
for the Devtoberfest planner's Session.host field. Joins Advocates -> Users
for email; derives a display NAME. Aliases all columns UPPERCASE (consumer
synonym resolve rule) and rides the existing tutorial_reader/# roles + HDI
leg -- no mta.yaml or binding change.

Verified against tutorials-hana DEV via hana-cli (column casing + join +
ISACTIVE BOOLEAN predicate).
The tag-tree-topics feature (#2099) added GET /content/topics/:slug,
GET /content/topics-index, and POST /content/publish/render-topics to
srv/server.js. These are public prod content surfaces mirroring the
/content/concepts* routes, not tutorial-draft author-preview endpoints,
so they belong in ALLOWLIST_ONLY_ON_SRV (same rationale as concepts/
authors/advocates/puzzles) rather than being mirrored into srv-qa.

Fixes the failing postbuild:apps route-drift guard in the unit CI job.
…llowlist

fix(ci): allowlist /content/topics* in srv-qa route-drift guard
feat(cross-container): ADVOCATE_VALUE_HELP_V1 view for planner Session.host (#2102)
_guardPublished rejected any PATCH/activation payload that merely *contained*
the published flag. Fiori draft activation echoes the whole entity (including
an unchanged published:true/false), so a non-SuperAdmin Admin editing only a
group/mission description or title was wrongly rejected with 403 'Only
SuperAdmin can change the published state'.

Compare the incoming published value against the persisted active-row value;
reject only on a genuine change. CREATE keeps the false-default exemption.

Adds test/admin-published-guard.test.js.
…-guard

fix(admin): stop prompting non-SuperAdmin for publish rights on description edits (#2111)
Overview section tags rendered as plain <span>s while the header chips
deep-linked to /tutorials/?tag=<slug>. Zip displayTags with
displayTagSlugs in tutorial-meta.html and emit <a> anchors matching the
header pattern (License stays label-matched + skipped, span fallback when
a slug is missing). Add a11y hover/focus styling for the link variant.
…2110)

Distinguish live-streamed from prerecorded Devtoberfest sessions with a
visual badge and a Format filter across all three views (grid, calendar,
schedule table).

- Feed: normalize BROADCASTINGPREFERENCE -> 'Live' | 'PreRecorded' | null
  (srv/lib/devtoberfest-feed.js); select the column in the schedule route.
- Shared pure helper broadcasting.ts (badge shape, FORMAT_FILTER_OPTIONS,
  matchesFormat) so every view renders the tag/filter identically.
  null/unset preference => no badge and excluded from Live/Prerecorded filters.
- Grid, calendar (Month/Week/Day + toolbar), and schedule table all gain the
  badge + Format filter; filter state deep-links via url-state.
- DetailPanel shows a Format meta row.
- Unit tests: broadcasting helper, feed normalization, url-state round-trips,
  and per-view badge/filter behavior.
…ew-tags

fix(tutorial): make Overview tags clickable (#2112)
…e-vs-recorded

feat(devtoberfest): Live vs Prerecorded session tag + format filter (#2110)
parseTitlePath split on legacy '>'/'--' separators that never appear in
live Tags.titlePath data (which uses ' : ' for facet, ' / ' for hierarchy
levels), collapsing every tag to facet='' + one whole-path segment — the
flat /topics/ tree Tom reported. Now split facet on ' : ' and levels on
' / '. buildTopicSlugMap derives the slug from the FULL titlePath so
already-published topic-<slug> BLOB keys/URLs stay byte-stable, with an
index suffix for rare collisions (dropped the facet-qualified branch).
fix(topics): nest /topics/ tree by parsing real titlePath separators
Prep for the 1.21.0 PROD release (promotion PR to main follows once this lands in DEV).

- .deploy/mta.yaml 1.20.1 → 1.21.0 (minor — feature-bearing DEV delta since 1.20.1)
- Refreshed hugo/data/whats_new.json: +27 entries (total 366) summarizing merged PRs since #2078
chore(release): bump MTA 1.20.1 → 1.21.0 + refresh What's New digest
@jung-thomas
jung-thomas marked this pull request as ready for review September 3, 2026 01:33
@jung-thomas
jung-thomas merged commit df59efd into main Sep 3, 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