Repository navigation
release v1.40.3 DEV to main promotion - #2871
Merged
Merged
Conversation
- LegacyRedirects.csv row 018: /abap -> https://pages.community.sap.com/topics/abap (repo seed drifted from live PROD DB, edited out-of-band; realigning source of truth) - redirects smoke: expect the Community ABAP topic URL, not the old same-origin path - content-signal smoke: probe a served HANA tutorial page (/tutorials/abap-dev-create-table/) instead of /tutorials/, which now 301s to /tutorial-navigator/ (yielded null headers) Verified: both smoke files 7/7 green against live PROD.
…smoke fix(smoke): align /abap redirect seed + smoke probes to live behavior
The tutorial-variables island JS was gated by (not site.Params.qa)
(not site.Params.previewMode), so interactive {{var:NAME}} fields never
mounted on /tutorials-qa/ — the token flattened to its frontmatter
placeholder with no input field. Authors previewing a tutorial in the QA
channel could not verify the variables feature before promoting to prod.
Drop the qa/previewMode conditions from the island loader, keeping the
.Params.variables guard so variable-free tutorials still ship no extra
script. The panel div and config JSON were already gated only by
.Params.variables (render on QA), and sync-island-manifest-qa.cjs copies
the whole island manifest, so the tutorial-variables fingerprint is
already available to the QA build — no other wiring needed.
feat(#2811): load tutorial-variables island in QA preview
…t, no code change)
…-variables feat: author-fixed and hidden tutorial variables
Trailing substep content after an [OPTION] group (prose, images, `> [!TIP]` alerts) was leaking as raw markdown inside a code block on rendered tutorials (e.g. hana-clients-install step 3). Root cause: a Hugo shortcode that emits a block-level <div> (os-options/os-panel) closes the surrounding tight numbered list regardless of source indentation (verified against production-pinned Hugo 0.147.7). Once the list closes, Goldmark measures the still-4-space -indented tail from column 0, where the >=4-space rule turns it into a literal indented-code block. Commit 19db8d0 deleted the "Bug B" tail-dedent on the false premise that indented wrappers keep the list open — the Hugo renders disprove that; the list always closes, so the dedent is mandatory. Fix (two parts): - Parser: restore the tail-dedent in convertOptionBlocks — trailing substep content is dedented to column 0 so it renders as sibling markdown; the author's `N.` ordinal reopens the list as <ol start="N"> and numbering continues. - CSS: split the trailing-figure rule so `margin-top/bottom` sets the rhythm without the `margin` shorthand clobbering `margin-left: 1.5rem` — a latent bug the parser fix exposes (the trailing image otherwise jams to the far-left column). Verified before/after on the real production path (composeTutorial -> renderHugoFrontmatter {{% tutorial-step %}} -> Hugo) across hana-clients-install/-hdbsql/-jdbc/-golang: all leaked BEFORE, all CLEAN AFTER. New render-level regression test test/unit/option-tail-leak.test.ts reproduces the leak via the percent-shortcode path (the only path that reproduces it) and asserts the corrected contract. Updated two tests that encoded the old (impossible "nest in <li>") behavior. Design: docs/superpowers/specs/2026-10-20-option-block-rendering-design.md
…rendering fix(options): dedent OPTION-group trailing content to col 0 (#2820)
/app-space/?eventId= worked but ?eventid= (or any other casing) silently
fell back to the default event, because URLSearchParams.get('eventId') is
case-sensitive. Iterate the params and match the key case-insensitively so
any casing of the eventid key resolves the event.
Adds a regression test asserting the backend getAppSpaceProgress call carries
the URL-supplied id (not the default 0) for eventId/eventid/EVENTID/EventId.
…-insensitive fix(app-space): read eventId query param case-insensitively
Adds GET /api/my-prize-barcode?eventId=<legacyId> — a session-authenticated
endpoint that mints a QR PNG encoding { uid: <Users.ID>, payload: { eventId } }
for the logged-in visitor. Identity is resolved from the XSUAA session via
resolveDbUserScoped; client cannot inject a uid via query string.
Cache-Control: private, no-store (identity-bearing; no CDN/browser cache).
401 when no DB user resolves; 500 on QR generation failure.
Route registered immediately after app.use(basicAuthMiddleware) in server.js,
alongside the existing /api/qrcode handler.
Tasks 3 + 4: - Add pure helpers checkpointBarcodeUrl / shouldShowCheckpointBarcode in checkpoint-barcode.ts (testable without mounting the component) - Wire authenticated ref from getAppSpaceProgress data.authenticated - Replace dead PRIZE/EARNED QR block with CHECKPOINT barcode section: shows <img src="/api/my-prize-barcode?eventId=..."> when track complete + authenticated; shows sign-in hint when not authenticated - Remove dead qrCodeUrl helper (no other callers confirmed via grep) - Add 10 tests covering URL shape, gate conditions, and render paths
…helper shouldShowCheckpointBarcode now takes (item, unlocked, authenticated) instead of (track, index, authenticated), eliminating the copy of the CHECKPOINT unlock logic that was already in isItemUnlocked(). The component wrapper passes isItemUnlocked(track, index) as the unlocked arg — single source of truth. Tests updated to the flat signature.
- getContestant now accepts accountNumber as a UUID string (Users.ID), dropping the parseInt / legacyId lookup path. - Adds eventId: Integer param; resolves Events by legacyId then scopes PrizeRecords count to that event_ID. Degrades gracefully (no event filter) when eventId is absent or unresolvable. - Returns claimedCount: Integer + prizeRecords: '<N> of 2 prizes claimed' replacing the per-prize-title enumeration. Completion counts unchanged. - claimPrize is untouched; all existing ownership tests still pass. - New test: test/unit/scanner-contestant-count.test.js (4 cases: scoped count, event isolation, completion fields present, unknown UUID → 404).
Add ScannerService.claimPrizeForContestant(accountNumber, eventId):
- Inserts a CLAIMED PrizeRecords row for (user UUID, event legacyId) iff
current CLAIMED count < 2; returns { claimedCount, message }.
- Atomicity: uses SELECT.forUpdate() on the user row to acquire an exclusive
row-level write lock before reading the prize count. On HANA this
serializes concurrent calls for the same user. On SQLite (unit tests)
forUpdate() is a no-op but Node.js single-threaded event loop serializes
the async chain within one cds.tx.
- Old claimPrize kept in place (still works on legacy pre-existing rows).
- legacyId left null on new rows — nullable on HANA, no @mandatory in CDS.
Tests: test/unit/scanner-claim-capped.test.js (5 tests, all pass)
(a) 1 CLAIMED → claim → claimedCount===2, new row exists
(b) 2 CLAIMED → claim → cap message, no new row
(c) Promise.all two concurrent claims from count 1 → final count 2
…5 regression)
scanner-service-auth.test.js seeded { ID: 'u-alice', legacyId: 8001 } but
called getContestant({ accountNumber: '8001' }) — the legacyId. Task 5 changed
getContestant to resolve by Users.ID (UUID), so '8001' no longer matched.
Changed all three call sites to accountNumber: 'u-alice' (the UUID). Auth
assertions (MobileApp scope guard) are unchanged.
getAppSpaceProgress / getEventProgress emitted one output item per CompletionPathItems row and hard-coded each path's description to the mission's. Two visible bugs on the App-Space event page: 1. A path whose item is a GROUP rendered "0 tutorials" — the GROUP row was emitted verbatim as type:'GROUP', which the frontend island (filters type==='TUTORIAL') dropped. Now expand GROUP items into their child tutorials via GroupPathItems (mirrors build-catalog.js), batching the child TaskRecord lookups so progress still renders. 2. Every path card showed the same mission description. Now use the path's own CompletionPaths.description (fallback to mission). Shared paths-building extracted to buildPathItems/expandGroupTutorials/ groupChildLegacyIds in path-item-task-resolver.js so both twin handlers stay in sync. Adds regression tests (GROUP expansion + per-path description) to test/app-space-fk-resolution.test.js.
…sion-path-desc fix(app-space): expand GROUP path items + use per-path description
- types.ts: add eventId to ScanResult.payload; add claimedCount to ContestantData
- useScannerApi.ts: getContestant(uid, eventId) passes eventId in URL;
replace claimPrize with claimPrizeForContestant(uid, eventId) returning
{claimedCount, message}; old claimPrize wrapper removed (was dead code)
- ScannerApp.vue: parseScanData reads payload.eventId; loadContestant passes
eventId to getContestant and stores claimedCount; template shows
"Prizes claimed: N of 2"; handover button disabled at claimedCount >= 2;
on success shows returned message and refreshes via getContestant;
manual placeholder updated to new QR shape
- test: scanner-claim.test.ts covers URL shape for both new functions (7/7 pass)
…(Task 5 contract)
…ybrid test Finding #1 — UI5 App at /scanner-ui/ still used old payload.recordId shape and called legacy claimPrize(). Updated to new contract: - CONTESTANT_DEFAULTS: recordId/status -> eventId/claimedCount - _getContestant: now accepts (uid, eventId) and sets /claimedCount from result - _processScanResult: reads payload.eventId (Number), drops recordId/status - onClaimPrize: calls /claimPrizeForContestant(...) with accountNumber + eventId; caps at claimedCount >= 2 (shows MessageBox.warning); shows returned message - View: primary prize display is "N of 2" counter; button label "Record prize handover"; button disabled when claimedCount >= 2 or not loaded; removed stale recordId/status sub-line from identity header Finding #2 — hybrid test scanner.getContestant call passed seed.userLegacyId (numeric legacyId); getContestant now resolves Users by ID (UUID). Fixed to pass seed.userId (UUID). No assertions weakened. Needs npm run test:hybrid to confirm on real HANA.
…ckpoint-qr # Conflicts: # srv/developer-service.js
…oint-qr feat(app-space): checkpoint prize barcode + capped scanner handover
AppSpace track view passed averageTimeToComplete (stored in minutes, from frontmatter `time`) into formatTime(seconds), which divided by 60. A 15-min tutorial rendered as "0 min" and 30-89 min as "1 min", while the tutorial object page and mission/group cards render the same value as minutes directly. Treat formatTime's input as minutes (drop the /60); hr/min rollover now works on the real minute count. Adds unit coverage asserting 15 -> "15 min" and 45+45 -> "1 hr 30 min" (both fail against the old /60 behaviour).
…e-units fix(app-space): render tutorial time as minutes, not seconds
GET /api/my-prize-barcode returned 401 for every authenticated visitor,
so the App Space checkpoint "Claim your prize" barcode rendered as a
broken image. Two defects, both fixed:
1. The route was a bootstrap `app.get('/api/my-prize-barcode', handler)`
registered on Express BEFORE CAP's protocol middleware chain, so the
XSUAA auth strategy never ran — cds.context.user/req.user were null
for every request. Mount cds.middlewares.context()/auth() in front of
the route (same lazy pattern as /api/alerts/me, /admin/analytics/export).
2. The handler resolved identity with the read-only resolveDbUserScoped,
which returns null for a logged-in user without a provisioned DB row
yet → 401. Switch to the canonical tiered getOrProvisionDbUser(req,
{ sapId }, ['ID']) — the same front door the Devtoberfest/gameboard
write path uses (#2730/#2651), so social / IAS / GitHub / null-sapId
logins map to their one canonical Users row. The scanner
(ScannerService.getContestant) trusts the uid in the scanned QR
verbatim, so the barcode must encode exactly that canonical Users.ID.
Verified live (cds watch, mocked auth): anon → 401, authenticated →
200 image/png (200x200 QR). New integration test boots the real server
to guard both defects (a handler-only unit test cannot catch defect #1).
…e-401 fix(#2861): App Space prize-barcode 401 for logged-in users
…l 401 fix) The App Space checkpoint barcode still 401'd for logged-in visitors after the previous #2861 fix. Root cause: on a raw Express bootstrap route CAP's auth() middleware populates the authenticated identity on cds.context.user (the AsyncLocalStorage continuation), NOT on req.user — req.user is only set in CAP service handlers and in the mocked-basic-auth test harness. The handler read bare req.user, so every real XSUAA/IAS login resolved to anonymous → provision returned null → a hard 401. The integration test stayed green because that harness DOES set req.user, hiding the production-only defect. Fix (matches the existing idiom used by the working /api/alerts/me and /auth/user routes, and the devtoberfest Express routes): - prize-barcode-handler.js: resolve `req.user || cds.context.user`, guard the anonymous sentinel, and provision via `provisionDbUser(user, ['ID'])` — the canonical get-or-create for raw Express routes (tiered iss/sub resolver, mints on first visit; takes the user object directly, no CAP-request .tx coupling, unlike getOrProvisionDbUser). - test/unit/prize-barcode-handler.test.js: rewritten to the new contract and adds the operative regression guard — identity present on cds.context.user, absent on req.user, must 200 (not 401). - Corrected the misleading "bootstrap bypasses the middleware chain" comments in srv/server.js and the integration test; the eager-bootstrap middleware mount is correct (cds.middlewares is a lazy getter, populated on first access). Verified: unit 8/8, integration (real-server boot, in-memory sqlite) 2/2, package-boundaries OK.
…arcode fix(#2861): prize-barcode 401 — resolve identity from cds.context.user
- Add temporarilyUnavailable/unavailableReason fields to BrowseCardItem
(scripts/fetch-tutorials.ts) and CardItem (hugo-apps/src/shared/types.ts)
- Populate both fields in buildAllCards mission literal from mMeta lookup
- Add badge element to MissionCard.vue (v-if="item.temporarilyUnavailable")
and card-mission.html Hugo partial ({{- if $item.temporarilyUnavailable -}})
immediately after the nav-card__type--mission div
- Add .nav-card__unavailable-badge CSS rule to card.css reusing badge visual
conventions (orange background, white text, rounded, small caps)
- Add temporarilyUnavailable:true to mission fixture in cards.fixtures.json
so parity test exercises the badge branch
- Regenerate card-mission.expected.html, card-tutorial.expected.html,
card-group.expected.html, and browse-page-1.html via regen-card-parity-fixtures.sh
- Fix regen script: pass (dict "item" ...) to card partials which expect
.item context, not a raw item (pre-existing script/partial interface mismatch
that caused the partial to silently produce empty fixtures)
- Add two new vitest cases: positive (flag set) and negative (flag absent)
in fetch-tutorials-browse-channel.test.ts
Both vitest suites pass (20 tests total).
…pspace-window # Conflicts: # srv/developer-service.js
…ace-window feat(#2866): gate App Space completions to the event time window
…able feat(#2864): Temporarily Unavailable toggle for mission paths
This was referenced Oct 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release 1.40.3 — DEV → main promotion
Patch release promoting the fixes and features merged to
DEVsince 1.40.2 tomainfor a PROD deploy.Version bumps
tutorials-ims).deploy/mta.yamltutorials-mcp)mta-mcp.yamlChanges in this window
eventIdcase-insensitively.value) and hidden (display) tutorial variables./abapredirect seed + post-deploy smoke probes to live behavior.What's New digest
hugo/data/whats_new.jsonregenerated from the 14 merged PRs in this window (12 substantive + the 1.40.1/1.40.2 release entries).