Release 1.27.0 → main (production) - #2415
Merged
Merged
Conversation
…-event flash (#2314) The app-space page is a public kiosk/QR landing. An anonymous visitor opening it with ?eventId=<id> saw the DEFAULT event, not the one in the URL: getAppSpaceProgress required authenticated-user, so anon requests 401'd and the client fell back to the static default-event JSON. - srv: getAppSpaceProgress is now @requires:'any'. It already degrades gracefully for anon (resolveUserSapId → null → empty progress); only public content (event name/description/tracks/tutorial URLs) is exposed. New `authenticated` flag in the payload tells the client whether to enable the logged-in-only realtime/confetti path — truthy even before a Users row exists (identity from the JWT user_uuid). - client: read the payload before deriving login state from the server's `authenticated` flag (a 200 no longer implies a login). Gate the hero text on !loading with a skeleton so the computed default name ('SAP TechEd') never flashes before the URL event resolves. Tests: anon + authenticated getAppSpaceProgress server tests; anon event-resolution + no-flash island tests.
…e-anon-event fix(app-space): correct event for anonymous visitors + no default-event flash (#2314)
The default socket.io transport (polling→upgrade) starts with a long-polling handshake whose follow-up POST 400s behind the multi-instance CF approuter, leaving display-app stuck 'Reconnecting…'. Match the sibling clients (event-display, gameboard, app-space) which already pin transports:['websocket'].
…-app-ws fix(display-app): pin websocket-only transport (#2306)
…2308) provenance-keys.js read process.env.PROVENANCE_SIGNING_KEY directly, but a BTP Credential Store binding does not auto-inject entries into process.env. A key rotated in via /admin-ui/#secrets lands in the credstore, so provenance never saw it -> jwks.json empty + provenance endpoint 503 on PROD. Route the read through resolveSecret('PROVENANCE_SIGNING_KEY') (credstore -> env fallback -> null), the same seam as CONTENT_API_KEY and every other secret, matching the #2245 design intent. Env fallback is preserved for local/dev. - add regression test that primes the resolver cache (not process.env) and asserts the signer loads - register PROVENANCE_SIGNING_KEY in seed-secrets so the presence/expiry cron surfaces a missing key - fix gotchas doc line that wrongly claimed the credstore binding surfaces the key as an env var
Import @cap-js/ord's OpenResourceDiscoveryService model into the project's
own srv model (srv/ord-annotations.cds) so it compiles into the production
CSN. Previously cds build --production treated the plugin-contributed
cds.requires["SAP ORD Service"] model (in node_modules) as an external
required service and omitted it from gen/srv/srv/csn.json; the deployed
runtime loads only that precompiled CSN and never instantiated the service,
so its init() never registered the express routes -> 404 on both
/.well-known/open-resource-discovery and /ord/v1/documents/ord-document.
Verified: cds compile srv now yields the local service
{kind:service, @path:'/.well-known/open-resource-discovery', @rest};
dev server serves both endpoints 200 (32 apiResources incl. 5 mcp).
Approuter already routes both paths anonymously ahead of the catch-all;
plugin defaults to Open access. Un-skip the two ORD smoke tests.
…y-2308 fix(provenance): read signing key credstore-first via resolveSecret (#2308)
…-2307 fix(ord): serve ORD discovery endpoints at runtime (#2307)
Author-facing skill to promote a QA-previewed tutorial from its private <product>-Contribution repo (QA source) to the canonical public sap-tutorials/Tutorials repo (PROD source), opening a cross-linked PR in each. Handles create (first promotion) vs update, resolves each repo's default branch dynamically (Tutorials=master, -Contribution=main), and drift-guards updates before overwriting PROD. Complements sap-tutorial-dual-pr (edit/mirror); this covers QA->PROD promotion. Closes #2310.
…ill-2310 feat(skills): tutorial-qa-to-prod author skill (#2310)
…odes (#2311) Ingest rich Devtoberfest Planner sessions (title/abstract/speaker/YouTube) from the cross-container facade (external.devtoberfest.*) into the Knowledge Graph as a new Phase-4 external-content type, DevtoberfestSessions, linked to concepts via predicate 'presents'. Sessions also join the semantic-search 'external' corpus and surface in concept-page neighborhoods/related content. - Schema: DevtoberfestSessions + DevtoberfestSessionConceptLinks (db/external-content.cds), with embedding/embeddingVec for search. - Projection: buildDevtoberfestSessionTriples + a session->tutorial bridge edge (aboutTutorial) for TUTORIAL-typed Planner Activities, so sessions connect into the tutorial subgraph instead of sitting isolated (srv/lib/kg-projection.js; TTL 730d in external-content-ttl.js; short-code 'dtf' in kg-explore-data.js). - Cron: fetch-devtoberfest-sessions-job.js + extractor devtoberfest-session-extract.js, scheduled Mon+Thu 04:43 UTC. Double-gated (KG master switch + KG_DEVTOBERFEST_SESSIONS_ENABLED) and fail-closed when cross-container Leg B isn't deployed. - Feature flag: DB-backed flag.kg.devtoberfestSessions (kind:'db', default OFF, admin-editable via /admin-ui/#featureFlags) — no env var (survives blue-green MTA deploy). - Semantic search: DevtoberfestSessions added to EXTERNAL_SOURCES + the freshness-corpus embedding backfill. - Sidebar: resource-type config entry + OtherResource widening. - Tests: extractor, projection (incl. bridge edge), fetch job (#708/budget/ links/flag gates), semantic-search external corpus; lockstep counts bumped. - Docs: knowledge-graph.md section + cross-container link registry update. Ships flag-gated + fixture-tested so it merges to DEV independently of Leg B; enable only after Leg B (ACTIVITY_SESSION_V1 synonym+grant) is deployed.
Follow-up to the KG/search integration: make the new devtoberfest-session nodes and their presents/aboutTutorial edges renderable in /graph/explore-data and the Explore UI, and satisfy the slug-lookup static guard. - KG_QUERY.hdbprocedure EXPLORE_GRAPH_BULK: add kg:presents + kg:aboutTutorial to the edge-predicate FILTER allowlist (9 -> 11 predicates). - Explore + related-graph type unions: add 'devtoberfest-session' NodeType and 'presents'/'aboutTutorial' PredicateType (app/explore/src/types.ts + hugo-apps/src/related-graph/types.ts mirror; OtherResource widened with the session-specific fields). - Explore UI filter/render maps: ALL_NODE_TYPES + ALL_PREDICATES (useFilters), NODE_COLORS + EDGE_COLORS (ExploreGraph.vue), SECTION_ORDER/LABELS/expanded (MobileTypedList.vue) — the Record<NodeType|PredicateType> maps that would otherwise drop the new type/edges from the graph. - Mark the two write-path-canonicalized slug lookups in the fetch job (// slug-canonical: write-path-canonicalizes) to pass check-slug-lookups.
…2311 feat(kg): Devtoberfest sessions as first-class KG + semantic-search nodes (#2311)
…eed (#2312) Unit F FOUNDATION for TechEd 2026 in the KG: - db/external/teched.cds: TechEdSessions/Speakers/Tracks + junction + KG link table (external ns, cuid+managed, delta-ingest chassis: sourceId/slug/contentHash/lastExtractedHash/firstSeenAt/lastSeenAt/pinUntil). Imported from external-content.cds so CAP loads it. - srv/lib/teched/rainfocus-fetcher.js: fetchAllTechEdSessions() over both venues (Virtual tev26, Berlin te26) via POST events.rainfocus.com/api/search; retry+jitter, AbortController timeout, incremental MAX_BODY_BYTES cap, Promise.allSettled, dedup, past/invalid filtering, _fetch seam. - srv/lib/teched/normalize.js: order-independent contentHash of source fields, kebab slug dedup, row normalizers. - srv/lib/teched/seed-core.js + scripts/seed-teched.cjs: idempotent SELECT-then-UPSERT (source-owned cols only; never overwrites pinUntil/lastExtractedHash), junction add+prune; dry-run default, --commit/--force/--file. - /build/teched in srv/server.js: explicit public projection, LargeString abstract/bio/description read in dedicated LOB-only queries (LOB-safe rule), Cache-Control 60s. - Tests: teched-normalize/fetcher/seed (21 green). Fixtures under test/fixtures/teched/. RainFocus contract confirmed reachable live; per-venue widget/apiProfile IDs are Akamai-gated and TBD — fixture is constructed, flagged verify-live in srv/lib/teched/README.md.
feat(teched): RainFocus fetcher + data model + seed + /build/teched feed (Unit F, #2312)
- New docs/developers/reference/teched.md: RainFocus contract, TechEd* data model + using side-effect gotcha, idempotent seed, /build/teched feed, and the planned cron/page/KG/search/Devtoberfest cross-link units (mirrors channels.md structure/depth) - CLAUDE.md Top Gotchas: one bullet linking the new doc - rebuild-content-workflow.md: note that /teched/ is a page-<key> HANA page baked in build:all + published via the rebuild workflow
Unit 10 of #2312. Adds upcoming SAP TechEd 2026 sessions (from external.TechEdSessions) to the homepage events band as always-on EventCards, region-agnostic like Devtoberfest, each linking to its session URL (falling back to /teched/). Gated behind the new TECHED_HOMEPAGE_ENABLED DB feature flag (ImsConfig key flag.homepage.teched, default OFF, dev-only). Fail-open: an empty catalog, an unset/cold flag cache, or a query error yields no TechEd cards, so merging is inert until an admin flips the flag. - srv/homepage-service.js: _techedAlways(db) helper, merged into _communityEventsForBand alongside manual/Devtoberfest/CodeJam sources. - srv/lib/feature-flags/registry.js: register TECHED_HOMEPAGE_ENABLED. - test/unit/homepage-events-teched.test.js: flag off/on, url fallback, virtual venue mapping, empty catalog, past-session drop.
…Unit 3) Add TechEd 2026 sessions to the knowledge-graph projection so sessions surface as graph nodes linked to concepts, their track, and speakers. Additive and TTL-gated; empty until Unit 2's job populates TechEdSessionConceptLinks. Mirrors the community-event / devtoberfest-session external-content-type pattern exactly: - KG_IRI_PREFIXES + iri* helpers for teched-session/speaker/track. - buildTechEdSessionTriples: node + literals, concept `covers` edges, structural `inTrack` (session→track) and `presentedBy` (session→speaker) edges, plus track/speaker nodes (emitted only when referenced by a visible session). Date-aware TTL (scheduledEnd + 30-day grace, falls back to scheduledStart), mirroring community-event gating. - loadFixtures block reading TechEdSessions/Tracks/Speakers, the session↔speaker junction, and TechEdSessionConceptLinks from the CDS snapshot (LOB-safe; fail-open). - Section 16 call in projectFromFixtures. - external-content-ttl.js: 'teched-session' key (null → date-aware). - kg-resource-type-config.js: teched-session entry (priority 100). - kg-explore-data.js SHORT_BY_TYPE entries (lockstep) + KG_QUERY EXPLORE_GRAPH_BULK predicate allow-list gains kg:inTrack/kg:presentedBy so TechEd nodes surface in /graph/explore-data. Tests: new build-teched-session-triples unit test; lockstep + config count assertions updated. Full DB-bound projectTriples path verified against in-memory SQLite (loadFixtures FK mappings); the full graphRebuild SPARQL round-trip is HANA/hybrid-only, so the triple-builder unit test is the primary gate.
…ks (#2312) Unit 9: relate Devtoberfest and TechEd sessions via shared KG concepts. - New srv/lib/teched-devtoberfest-crosslink.js: pure overlap-ranking helpers (top 3 by shared-concept count) plus flag-gated, fail-open DB orchestrators. Devtoberfest session -> tutorial (Activity.TASKSLUG) -> TutorialConceptLinks; TechEd session -> TechEdSessionConceptLinks; both reference the same Concepts. - assembleFeed attaches relatedTechEdSessions per Devtoberfest session. - /build/teched attaches relatedDevtoberfestSessions per TechEd session (reverse path scoped to the current Devtoberfest edition, symmetric with the edition-scoped forward feed). - New DB feature flag TECHED_DEVTOBERFEST_CROSSLINK_ENABLED (flag.teched.devtoberfestCrosslink, default OFF, dev-only). - Fail-open: empty arrays when the flag is OFF, concept links are cold, or the cross-container Devtoberfest planner facades are absent (unit SQLite) -- never throws into feed assembly or the build handler. - 60s in-process cache for the TechEd concept map (feed + iCal + RSS + build all share the loader). - Unit tests: pure ranking, cap-3, assembleFeed wiring, and SQLite-backed orchestrator (flag ON populates, flag OFF empty, facades-absent fail-soft).
Unit 6 of #2312 — let agents query the TechEd 2026 session catalog. - srv/lib/mcp-teched-search.js: handleSearchTechEd() modeled on handleSearchEvents. Validates/clamps inputs, filters by venue (BERLIN|VIRTUAL), track (slug or case-insensitive name substring), and free-text over title+abstract; orders by scheduledStart; fails open []. The abstract LargeString (NCLOB) is never used in a SQL predicate — it's read in a dedicated LOB-only query and matched in JS, so behaviour is identical on SQLite + HANA (CLAUDE.md LOB rule). Resolves track slug and speaker names via separate queries. Exports mapRow(). - srv/search-service-mcp.cds: @requires:'any' search_teched function (query, venue, track, limit) mirroring search_events. - srv/search-service.js: .on('search_teched', handleSearchTechEd). - srv/mcp/mcp-manifest.js: advertise search_teched in the anonymous tier. - tests: new unit test (13 cases) + contract-test enumeration/params.
Filterable TechEd 2026 session browser mounting on #teched-sessions-grid-mount. Berlin/Virtual sections with a venue toggle, track facet chips, speaker dropdown, and text search over title/abstract/speaker/track. Filters sync to the querystring (deep-linkable). Loads from an embedded #teched-data blob or falls back to GET /build/teched. - filter.ts: pure filterSessions(sessions, state) — venue/track/speaker/query - url-state.ts: parse/serialize deep-link state (trimmed, venue-normalized) - App.vue + main.ts: island UI, accessible headings/labels - __tests__: filter, url-state, render (vitest) - vite.config.ts: teched-sessions-grid rollup entry
Make TechEd 2026 sessions visible to the HANA native property graph so
Louvain community detection and PageRank see them alongside the existing
concept/tutorial/mission/group/tag/product/category structure.
KG_PG_VERTICES_V — three new UNION-ALL arms:
- teched_session (keyed on SLUG, LABEL=TITLE)
- teched_track (keyed on SLUG, LABEL=NAME)
- teched_speaker (keyed on SLUG, LABEL=NAME)
All three carry the same ROW_NUMBER() OVER (PARTITION BY SLUG ...) dedup
guard as the concept arm: TechEdSessions/Tracks/Speakers.slug is
@assert.unique at the CAP runtime layer only, but the delta-ingest seed
(srv/lib/teched/seed-core.js) writes via raw db.run() keyed on sourceId
(not slug) and bypasses the service layer, so a re-slug race could leave
two rows sharing a SLUG and crash the whole KG workspace ([4907] range 1).
KG_PG_EDGES_V — three new UNION-ALL arms:
- covers teched_session -> concept (TechEdSessionConceptLinks,
filtered PREDICATE='covers', concept STATUS='ACTIVE')
- inTrack teched_session -> teched_track (TechEdSessions.track_ID)
- presentedBy teched_session -> teched_speaker (TechEdSessionSpeakers)
All use SELECT DISTINCT (defense-in-depth against duplicate EDGE_KEY).
Track/speaker vertex arms were added specifically to back the inTrack /
presentedBy edges (mirrors how #919 added mission/group vertices for the
partOf/inCategory arms) so no edge endpoint is a dangling vertex key.
Additive and HANA-only: these .hdbview artifacts live under db/src/ and are
deployed by the HDI deployer, never compiled by the SQLite unit-test model
(cds.requires.db[development] = db/sqlite). Column shapes/order/types and
NVARCHAR key sizing mirror the existing arms; new vertexType values fit the
String(16) persistence columns.
Create the SAP TechEd 2026 Sessions page that mounts the Unit 8 island
(#teched-sessions-grid-mount) and serves via the pages-from-HANA pipeline.
- hugo/content/teched/_index.md + hugo/layouts/teched/list.html: page shell
with <h1>, separate Berlin/Virtual section headings, per-venue <noscript>
fallback lists, and the content-hashed island via the island-src.html
partial. Uses a <div> wrapper (not a nested <main>) since baseof already
provides the sole <main> landmark — avoids axe landmark-no-duplicate-main.
- scripts/fetch-teched.ts: fetch ${CAP_BASE}/build/teched -> hugo/data/teched.json,
fail-open to empty {sessions,speakers,tracks} on error (mirrors fetch-channels).
Wired into build:all before build:hugo; teched.json gitignored (build artifact).
- srv/lib/page-key-map.js: add page-teched (teched/index.html) to IN_SCOPE_PAGES.
- approuter/xs-app.json: route /teched/ -> /content/pages/teched/ (anonymous).
- test/smoke/pages-routes.smoke.test.js: add /teched/ HTML route with marker.
The teched-sessions-grid island itself is Unit 8; until it lands the
island-src partial falls back to the bare /js path (expected).
…#2312) Unit 5 of #2312: make TechEd 2026 sessions searchable via the existing public semantic (vector) search. - db/external/teched-embedding.cds: extend TechEdSessions with dual-column embedding (embedding LargeBinary for SQLite, embeddingVec Vector(1536) for HANA), mirroring the ApiDocs/Samples/Devtoberfest convention. Loaded as a side-effect using from db/external-content.cds. - freshness-corpus-embedding-job.js: embedEntity now takes a textColumn override (TechEd body text lives in 'abstract', not 'description'); add a fault-isolated TechEdSessions pass so a missing table on an un-migrated env logs and continues instead of failing apiDocs/samples. Existing scheduler entry runs it (03:17 UTC). - semantic-search.js: add 'teched' to VALID_CORPORA and a TECHED entry to EXTERNAL_SOURCES (per-source descCol override); 'teched' scans only TechEd, 'external'/'all' include it. Dual HANA COSINE_SIMILARITY / SQLite JS cosine, fail-open []. - Tests: SQLite cosine gate for corpus 'teched' + 'external', and backfill via abstract column. HANA vector path can't run on SQLite; SQLite cosine test is the gate.
…t + delta cron jobs (#2312) Unit 2 of #2312. First corrects the merged foundation's fetcher (built against a wrong /api/search + separate-speaker-scrape guess) to the real, live-proven contract, then adds the scheduled delta ingest jobs. Fetcher (srv/lib/teched/rainfocus-fetcher.js): - POST events.rainfocus.com/api/sessions with rfapiprofileid/rfwidgetid headers (per-venue, env-overridable via RAINFOCUS_TE26/TEV26_PROFILE/WIDGET). - Offset pagination: from += size (size=50, server hard-caps at 50) until from >= totalSearchItems; empty-page safety break. Retrieves full catalog (~297 Berlin + 49 Virtual), verified live 2026-09-15. - Speakers extracted from embedded participants[] (speakerId, globalJobtitle, photoURL); tracks from attributevalues[attribute=='Track'] keyed on rf_attributevalue_id. utcStartTime "YYYY/MM/DD HH:MM:SS" normalized as UTC. - Routed through safeFetch (SSRF guard, #895); retry/backoff via img-cdn-retry helpers with Retry-After honored up to 60 s; streaming body-size cap kept. - normalize.js: toKebabSlug now delegates to shared slugify (diacritics). Jobs: - fetch-teched-sessions (weekly, Sun 05:43): fetch + runSeed upsert/delta/junction core (always); double-gated (KG master + KG_TECHED_SESSIONS_ENABLED, default OFF) fail-open concept-link enrichment with #708 crash-safety. - refresh-teched-sessions (every 6h :23): metadata-only upsert (seed-core metadataOnly mode) — never touches contentHash/lastExtractedHash. - Registered both in scheduler; wired TechEdSessions into gc ITERATION_SET + PER_TYPE_TTL_DAYS (teched-session, 730d); added KG_TECHED_SESSIONS_ENABLED flag. Tests: replaced constructed fixtures with a REAL trimmed capture (both venues); teched-feed.json derived from the real fetcher output; new teched-jobs e2e test (core insert, idempotent delta, KG gate, enrichment SELECTs, metadata-only refresh); updated scheduler-registry (53) + gc ITERATION_SET counts. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
feat(teched): real RainFocus /api/sessions fetcher + delta cron jobs (#2312 Unit 2)
…arch-2312 feat(teched): TechEd sessions in semantic search + embedding backfill (#2312)
feat(teched): /teched/ top-level page (Unit 7, #2312)
…h-2312 feat(kg): add TechEd sessions to HANA property graph (#2312)
feat(teched): teched-sessions-grid Vue island (#2312)
feat(teched): consolidate track color legend into filter chips (item 7/10)
…l-2392 feat(teched): speaker author-links + bio tooltip in detail pane (#2392 items 3, 9, 10)
fix(teched): construct sap.com session URL from sourceId+venue (items 2+8, #2392)
…t" (#2392) Speaker filter dropdown now sorts by last name and shows each speaker as "Last, First" instead of first-name-first. Adds lastNameKey() and displayNameLastFirst() helpers; single-token names are shown unchanged. The option value stays sp.slug so filter logic is unaffected. Shared surnames tie-break by full name for deterministic ordering. Issue #2392 item 4.
All-day activities (Developer Garage, AI Showfloor Expo, etc.) live under a SEPARATE RainFocus catalog tab (`tab.alldayactivities=<id>`), NOT in the regular `type=session` feed. This PR wires them in end-to-end. **Model** (`db/external/teched.cds`) - Add `allDay Boolean default false` to `TechEdSessions`. **Fetcher** (`srv/lib/teched/rainfocus-fetcher.js`) - New `parseAllDayActivity(item, venue)` — lenient parser (synthesises `allday-<sourceId>` code when absent, room from `location`/`venue` fallback, `allDay: true`). - `fetchAllTechEdSessions` issues a best-effort per-venue all-day POST (own try/catch; `allDayFilter` env-configurable; empty ⇒ skipped). - `parseVenuePayload` accepts `allDayItems`; dedup via `seenSourceIds` (regular session wins on collision). - `dropPast` never drops a row where `s.allDay === true`. - Defaults: Berlin filter `1742815606768001gh9v` (Tom's capture), Virtual unset; tab param `tab.alldayactivities`. **Normalize** (`srv/lib/teched/normalize.js`) - `normalizeSession` maps `allDay: raw.allDay === true` (part of hash). **Seed** (`srv/lib/teched/seed-core.js`) - `SESSION_COLS` includes `'allDay'`. **Feed** (`srv/lib/teched-feed.js`) - SELECT includes `allDay`; emits `allDay: isAllDay(r.allDay)`. - Shared `isAllDay(v)` helper (coerces `true`/`1` → boolean). - `upcoming=true` filter uses tagged-template CQL (safe ISO binding) with explicit `OR allDay = true OR scheduledEnd is null` carve-out so timeless all-day rows survive the filter. **UI** (`hugo-apps/src/teched-sessions-grid/App.vue`, `filter.ts`) - `allDay?: boolean` on `TechEdSession` interface. - `filteredAllDay` / `filteredTimed` computed splits. - Distinct `<section aria-label="All-day activities">` with "All-day" badge rendered above the timed grid; timed grid unaffected. - `test/unit/teched-allday.test.js` — parseAllDayActivity, dedup, fetchAllTechEdSessions all-day tab, normalizeSession allDay flag. - `test/unit/srv/build-teched-allday.test.js` — allDay flows through seed → /build/teched; upcoming filter keeps all-day rows. - `hugo-apps/src/teched-sessions-grid/__tests__/App.allday.test.ts` — all-day section renders, excluded from timed grid, badge, detail panel, filter across both sections. - `test/unit/teched-normalize.test.js` — allDay flag coverage extended. - `test/fixtures/teched/rainfocus-alldayactivities.json` — synthetic raw all-day tab capture (live params unconfirmed — see OPEN ITEM). - `test/fixtures/teched/teched-allday-feed.json` — derived feed shape. The exact live RainFocus request param name/value for the all-day tab is NOT yet confirmed against the real API. Parsing, model, seed, feed, and UI are proven against the synthetic fixture. Tune `RAINFOCUS_TE26_ALLDAY_FILTER` / `RAINFOCUS_ALLDAY_TAB_PARAM` when a live capture is available — a mismatch yields 0 all-day rows and never breaks the regular session ingest. - #2 iCal all-day gap: graceful 404 out of scope for this unit. - #3 NULL-first ordering on SQLite vs HANA: catalog << 1000 cap, latent. - #5/#6 UI/parser duplication: deliberate (scoped CSS + tested parseSession). - #7 mass UPDATE on first ingest: one-time benign, expected behaviour.
…2392 feat(teched): speaker dropdown sorted by last name, shown "Last, First" (#2392 item 4)
…ties-2392 feat(teched): list all-day activities like Developer Garage (issue #2392 item 6)
Adds a discrete 'Community Clubhouse' filter to the TechEd session browser, identifying Clubhouse sessions by room === 'Community Theater' (case-insensitive, trimmed). There is no dedicated track/flag. - filter.ts: new optional clubhouse facet on filterSessions; exported CLUBHOUSE_ROOM + isClubhouse() helpers (ANDs with existing facets, default off, backward compatible). - sessions-grid + schedule: Community Clubhouse toggle wired into filterSessions, hasActiveFilters, and clearFilters. - sessions-grid url-state: persists clubhouse=1 (parse/serialize/init/ applyFromUrl/watch) for deep-linking and back/forward restore. - calendar: equivalent toggle in its inline filter, reusing isClubhouse(); clubhouse=1 added to its url-state for deep-link parity with the grid. - tests: filter (case/trim/AND), grid App + url-state, and calendar App (toggle + deep-link). All 113 teched unit tests pass.
…ter-2392 feat(teched): Community Clubhouse session filter (#2392 item 5)
The loadTechEdFeed session query seeded its WHERE with `where`1 = 1`` and
bound the all-day flag as `allDay = ${true}`. Both trip the HANA hdb driver
at param-binding time ("Cannot set parameter at row: 1. Argument must be a
string"), so /build/teched returned {error:...} on DEV and every /teched
island (grid/schedule/calendar) failed to load real data. SQLite tolerates
both forms, so the in-memory unit tests passed and the regression only
surfaced against deployed HANA.
Build the WHERE incrementally without the 1=1 seed — the first active
predicate uses `.where`, later ones chain via `.and` — and use the SQL
literal `TRUE` instead of a bound boolean. Verified against DEV HANA: all
opt paths ({}, upcoming, venue, venue+upcoming) return rows; 344 sessions
carry the sap.com url, 2 all-day rows present, upcoming filter keeps them.
Note: the same `where`1 = 1`` pattern exists in srv/lib/mcp-events-search.js
and mcp-teched-search.js, and a bound `${true}` in mcp-channels-search.js —
latent HANA hazards on those MCP search paths, out of scope here.
Refs #2392
…-fix fix(teched): make /build/teched feed query HANA-compatible
…m HANA Two DEV-only /teched regressions surfaced testing issue #2392 on deployed DEV: 1. Duplicate speakers. RainFocus issues the SAME person a DISTINCT speakerId per venue catalog (te26 vs tev26), so anyone speaking in both Berlin AND Virtual arrived as two speaker records — showing "Name" twice in the sessionCode dropdown and, because only one record usually carries a photo, leaving many detail-panel avatars blank. fetchAllTechEdSessions now collapses speakers by normalized name after the venue merge, keeping the richer record (prefer photoUrl, then bio) and remapping every session's speakerSourceIds to the survivor so the (session, speaker) links still resolve in seed-core. Live feed had 85 such duplicate names (84 confirmed same-person by matching company; the 1 outlier is a company-string variant of the same person). Verified on the real 2-venue fixture: Philipp Herzig (both venues) folds 10 raw speakers → 9, and his Berlin session link is preserved. 2. Empty schedule + calendar. /teched/ is served from HANA (page-teched), but /teched/schedule/ and /teched/calendar/ had NO approuter route or page-key entry, so they fell through to the STATIC copy baked into the approuter mtar. That static copy was baked by build:all's fetch-teched against the still- broken backend (0 sessions) → both pages rendered empty. Add /teched/schedule/ + /teched/calendar/ approuter routes (→ page-teched- schedule / page-teched-calendar, auth none, like /teched/) and the matching IN_SCOPE_PAGES entries so publish-content uploads them to HANA and they pick up the healthy feed via the content rebuild, exactly like the grid. Refs #2392
…hana-speaker-merge fix(teched): dedup cross-venue speakers + serve schedule/calendar from HANA
…hedule/calendar (#2392) All-day activities (Developer Garage etc.) were under-counted: RainFocus lists 17/19 all-day items in BOTH the main catalog and the all-day tab. parseVenuePayload's 'regular session wins' dedup left those 17 tagged allDay:false, so the UI showed only the 2 tab-exclusive activities. Membership in the all-day tab is now authoritative for the allDay flag (richer timed data is still kept). Speaker photos were missing in the schedule + calendar detail panels: both islands built speaker objects without photoUrl and never set speakersEnriched, so DetailPanel rendered name-only cards. Both now enrich speakers (photoUrl/role/company/authorLogin/bio) into speakersEnriched — parity with the grid — so photos, author links, and bio tooltips render. Tests: added photo-render assertions (schedule + calendar), updated the both-tabs dedup test to assert allDay flips true, corrected stale speakersUpserted (10→9, Herzig cross-venue merge #2403) + authorLogin typing + calendar doc comment.
…-photos-2392 fix(teched): surface all 19 all-day activities + speaker photos in schedule/calendar (#2392)
…calendar (#2392) The srv pageServeHandler canonicalizes the /content/pages/<remainder> URL into a ROUTE and looks it up in IN_SCOPE_PAGES._byRoute. The approuter routes for the two TechEd subpages targeted /content/pages/teched-schedule/ and /content/pages/teched-calendar/ (dash), which canonicalize to routes /teched-schedule/ and /teched-calendar/ — NOT in the allow-list (the real routes are /teched/schedule/ and /teched/calendar/). Result: both subpages 404'd even though the page-teched-schedule / page-teched-calendar BLOBs were published correctly (verified: GET /content/pages/teched/schedule returns 200). Fix: target /content/pages/teched/schedule/ and /content/pages/teched/calendar/ (slash), matching the published route keys — same pattern as the working /teched/ → /content/pages/teched/.
…ter-route-2392 fix(teched): correct approuter target for /teched/schedule + /teched/calendar (#2392)
…otos (#2392) RainFocus hosts TechEd speaker photos at https://static.rainfocus.com/...; the img-src CSP directive did not allow that host, so every speaker photo was blocked ("violates the following Content Security Policy directive: img-src ..."). The photoUrl flows through the feed → island → DetailPanel correctly; the browser was blocking the fetch. Added https://static.rainfocus.com to img-src.
Speaker names in the TechEd detail panel/cards only linked when the speaker also
had a GitHub-login author page (author_index.json). Advocates like Rekha D R and
Witalij Rudnicki have /developer-advocates/{slug}/ pages but no author-index entry,
so they showed as plain text.
Add a name→advocate-slug matcher (author-match.js, mirrors the author matcher:
normalized-name, 1:1 unambiguous only, fail-open) and enrich speakers with
advocateSlug in fetch-teched.ts (roster read from hugo/content/developer-advocates/*.md
title+slug). The islands + shared DetailPanel now link via a speakerHref() that
PREFERS the advocate page (/developer-advocates/{slug}/) over the author page
(/authors/{login}/), falling back to author, then plain span.
Tests: advocate-matcher unit tests (1:1, ambiguity exclusion, fail-open),
DetailPanel tests (advocateSlug preferred over authorLogin; advocate-only link).
271 teched unit tests green.
Note: baked into hugo/data/teched.json (the blob the islands read); needs a
--ref DEV content rebuild after deploy for the links to appear.
fix(teched): allow static.rainfocus.com in CSP img-src for speaker photos (#2392)
…-2392 fix(teched): link speakers to their developer-advocate page (#2392)
…n in (#2409) The Sign in link on the Devtoberfest schedule/sessions/calendar pages (PointsBanner.vue) used a bare /login href. login-redirect.html defaults returnTo to '/' when the param is absent, so signing in from a Devtoberfest page bounced the user to the homepage instead of back to the page they were on. Build /login?returnTo=<pathname+search> at render time, matching the site header's profile-click flow. Adds a happy-dom test locking the returnTo behavior in.
…rfest-login-returnto fix(devtoberfest): Sign in from Devtoberfest pages returns to current page (#2409)
… the header EasyList ships an exact-class rule (##.adv-header), so uBlock Origin, AdBlock Plus, AdGuard etc. hid the entire Developer Advocates header — region/topic filters and search included — for any visitor with an ad blocker (issue #2405). 'adv' reads as 'advertisement' to the filter list. Rename the adv-* class namespace to dev-advocate-* across the advocates and advocate-profile islands (incl. DEV's new sessions/tutorial-rail sections) and the Hugo layout. Exact-class filter rules do not match dev-advocate-header, and it stays semantic/readable. Tests reference the same classes and were renamed in lockstep. Closes #2405
…lock fix(advocates): rename adv-* CSS namespace so ad blockers stop hiding the header (#2405)
70 new What's New entries (33 Feature, 30 Fix, 3 Docs, 3 Maintenance, 1 Performance) covering merged PRs since 1.26.0. Bump MTA version 1.26.0 → 1.27.0 for the production release. Generated with Claude Code
…atures-2374-wt docs(authors): list restored AI-authoring meta-tutorials (#2374)
chore(release): 1.27.0 — What's New digest + version bump
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.
Production release 1.27.0
Promotes DEV → main for the production deploy. 152 commits ahead of main.
.deploy/mta.yaml→ 1.27.0 (from chore(release): 1.27.0 — What's New digest + version bump #2414)Highlights since 1.26.0
Full list in
hugo/data/whats_new.json.Post-merge (not part of this PR)
Merging to
maindoes not redeploy CF — the MTA deploy is a separate, explicitly-authorized step. Deploy from a freshorigin/mainafter this merges.Generated with Claude Code