Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
685da84
feat(#2864): honor temporarily-unavailable path items in App Space
jung-thomas Oct 21, 2026
3eeb854
Merge pull request #2882 from sap-tutorials/worktree-2864-appspace-di…
jung-thomas Oct 21, 2026
51e148d
docs: design for App Space parallel paths (fork lanes + free-order cl…
jung-thomas Oct 22, 2026
4db8148
docs(#2887): implementation plan for App Space parallel paths
jung-thomas Oct 22, 2026
40af964
feat(#2887): classifyPathSegments — group path items by itemOrder int…
jung-thomas Oct 22, 2026
d4215a0
feat(#2887): buildPathSegments — emit fork lanes + synthetic lane pri…
jung-thomas Oct 22, 2026
17cd106
feat(#2887): server-side segment-aware unlock flags (composes with #2…
jung-thomas Oct 22, 2026
2ca7efb
fix(#2887): exclude prize items from isLaneComplete gate; add regress…
jung-thomas Oct 22, 2026
1a23c2e
feat(#2887): app-space-gating reads server unlocked flag, legacy fall…
jung-thomas Oct 22, 2026
c066db7
feat(#2887): render fork lanes + any-order clusters in App Space trac…
jung-thomas Oct 22, 2026
29f7fb4
test(#2887): app-space parallel e2e spec + admin editor verification
jung-thomas Oct 22, 2026
0a7e9ac
Merge remote-tracking branch 'origin/DEV' into worktree-appspace-para…
jung-thomas Oct 23, 2026
77771b3
Merge pull request #2888 from sap-tutorials/worktree-appspace-paralle…
jung-thomas Oct 23, 2026
25fafc8
fix(#2890): propagate GROUP temporarilyUnavailable to expanded lane c…
jung-thomas Oct 23, 2026
37f454f
Merge pull request #2891 from sap-tutorials/worktree-2890-group-unava…
jung-thomas Oct 23, 2026
44aa0be
fix(#2889): key isTrackComplete off the highest-order checkpoint
jung-thomas Oct 23, 2026
716fdf5
Merge pull request #2892 from sap-tutorials/worktree-issue-2889-highe…
jung-thomas Oct 23, 2026
c4b511d
fix(#2886): refuse deploy when Hugo fingerprint inputs are dirty
jung-thomas Oct 23, 2026
c5cfd52
ci(#2885): gate QA publish on approuter-served CSS/JS assets
jung-thomas Oct 23, 2026
f40c985
docs(#2886): recommend scoped `git stash push` over `git stash -u`
jung-thomas Oct 23, 2026
bc8b84c
feat(#2885): add approuter-gate decision + arg-builder to publish CLI
jung-thomas Oct 23, 2026
5ddcfea
feat(#2885): run approuter asset gate before CLI publish
jung-thomas Oct 23, 2026
ca7a927
docs(#2885): make post-deploy QA content publish mandatory
jung-thomas Oct 23, 2026
63ac933
docs(#2885): add stranded-asset regression note to gotchas
jung-thomas Oct 23, 2026
bd3bb36
Merge pull request #2893 from sap-tutorials/worktree-2886-dirty-tree-…
jung-thomas Oct 23, 2026
2362ac7
Merge pull request #2894 from sap-tutorials/worktree-2885-publish-ass…
jung-thomas Oct 23, 2026
081e013
fix(#2857): surface admin PAT scope in mint UI (gated to admins)
jung-thomas Oct 23, 2026
545f1fd
Merge pull request #2895 from sap-tutorials/worktree-issue-2857-admin…
jung-thomas Oct 23, 2026
b69edb1
fix(rebuild): no-op auto-dispatched rebuild for retired slug instead …
jung-thomas Oct 23, 2026
a964c5f
docs: shelved design for app-space shared-prize any-of (deferred, not…
jung-thomas Oct 23, 2026
69afc22
fix(app-space): align fork/join connectors to lane bubble centers (#2…
jung-thomas Oct 23, 2026
c861918
Merge pull request #2899 from sap-tutorials/worktree-app-space-parall…
jung-thomas Oct 23, 2026
4c2690f
Merge pull request #2897 from sap-tutorials/fix/retired-slug-rebuild-…
jung-thomas Oct 23, 2026
ac6b7e3
fix(build): stop committing generated CSS so deploy & rebuild agree o…
jung-thomas Oct 23, 2026
47d2d2c
Merge pull request #2900 from sap-tutorials/worktree-fix-generated-cs…
jung-thomas Oct 23, 2026
cb472f4
fix(app-space): connect fork/join bar to the backbone stem (#2896 fol…
jung-thomas Oct 23, 2026
7ba3910
Merge pull request #2901 from sap-tutorials/worktree-fix-fork-stem-ba…
jung-thomas Oct 23, 2026
6290681
docs(scanner): badge-scanner v2 design — camera fix, real prize count…
jung-thomas Oct 23, 2026
f9217be
docs(scanner): badge-scanner v2 implementation plan (5 tasks, TDD)
jung-thomas Oct 23, 2026
e74271c
feat(scanner): getContestant returns identity + claimed prize names (…
jung-thomas Oct 23, 2026
8c5b7c1
feat(scanner): add getEventStats event-aggregate function (#badge-v2)
jung-thomas Oct 23, 2026
8e0c7a9
fix(scanner): await nextTick before camera scan so video ref is mount…
jung-thomas Oct 23, 2026
24f4240
feat(scanner): identity line, claimed prize names, event stats panel,…
jung-thomas Oct 23, 2026
0187168
fix(scanner): event-scope card completion counts + POST claimPrizeFor…
jung-thomas Oct 23, 2026
60c2600
Merge pull request #2902 from sap-tutorials/worktree-scanner-badge-v2
jung-thomas Oct 23, 2026
bc52d31
fix(parser): preserve pseudo-tags inside inline code spans
jung-thomas Oct 23, 2026
fa4beb2
Merge pull request #2904 from sap-tutorials/worktree-fix-codespan-pse…
jung-thomas Oct 24, 2026
69c41af
fix(scanner-ui): absolute binding paths + remove duplicate rootView/r…
jung-thomas Oct 24, 2026
4ecd0b0
fix(scanner-vue): cross-browser camera via jsQR fallback
jung-thomas Oct 24, 2026
312d23c
Merge pull request #2905 from sap-tutorials/worktree-fix-scanner-both…
jung-thomas Oct 24, 2026
0b5d0ff
fix(#2574): add admin PAT scope to the /me/tokens/ mint UI
jung-thomas Oct 24, 2026
016686a
Merge pull request #2907 from sap-tutorials/worktree-2574-me-tokens-a…
jung-thomas Oct 24, 2026
ec9965b
fix(scanner): send CSRF token on Vue claim POST + surface contestant …
jung-thomas Oct 24, 2026
92db6bb
Merge pull request #2910 from sap-tutorials/worktree-scanner-vue-csrf…
jung-thomas Oct 24, 2026
1f0b07e
chore(release): 1.41.0
jung-thomas Oct 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .deploy/mta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ ID: tutorials-ims
# Bump this on each release you deploy — it's the version shown by `cf mtas`
# and in the mtar filename (tutorials-ims_<version>.mtar). Deploy is manual:
# `cd .deploy && mbt build && cf deploy mta_archives/tutorials-ims_<version>.mtar -e ../deploy/<env>.mtaext -f`.
version: 1.40.4
version: 1.41.0

# Top-level parameters (overridable per-env via deploy/<env>.mtaext).
parameters:
Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/rebuild-content-qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,39 @@ jobs:
- name: Highlight CDS code blocks (Shiki)
run: npm run build:highlight -- --dir hugo/public-qa

# [#2885] Pre-flight asset gate — the same guard rebuild-content.yml
# already runs (its "Guard - approuter serves referenced CSS + islands"
# step), now on the QA path — the surface stranded on 2026-10-22. Probes
# every /css + hashed island /js the rendered QA HTML references against
# the deployed QA approuter and FAILS before the HANA publish on any 404,
# so QA BLOBs are never poisoned with HTML the approuter can't dress. QA
# refreshes ONLY on its own tutorial-qa-updated dispatch (it does not
# self-heal like prod), so this gate is the only thing standing between a
# fingerprint-skewed deploy and unstyled /tutorials-qa/* pages.
- name: Guard - approuter serves referenced CSS + islands (QA)
# Share the publish step's skip condition (line ~405): an author
# source-error (#2384) leaves no rendered page for the slug, and
# check-approuter-assets.cjs resolvePages() die()s (exit 1) on a missing
# page — without this `if:`, a benign author typo would become a red
# infra run and file a false failure issue.
if: ${{ steps.source_errors.outputs.bad_slug == '' }}
env:
APPROUTER_URL: ${{ secrets.APPROUTER_URL_QA }}
# Match the publish step's PUBLISH_SLUG / PUBLISH_SLUGS expressions
# EXACTLY (lines 424/431) so the probe scope tracks the publish scope:
# a force-publish run ships the WHOLE catalog, so it must NOT pin the
# guard to one slug (that would gate 1 and strand ~1400 — the inverse
# of the bug this closes). check-approuter-assets.cjs reads both.
PUBLISH_SLUG: ${{ ((! inputs.force-publish) || inputs.allow-revert) && (github.event.client_payload.slug || inputs.slug) || '' }}
PUBLISH_SLUGS: ${{ (! inputs.force-publish) && github.event.client_payload.slugs || '' }}
run: |
SLUGS_ARG=""
[ -n "$PUBLISH_SLUGS" ] && SLUGS_ARG="--slugs $PUBLISH_SLUGS"
node scripts/check-approuter-assets.cjs \
--approuter-url "$APPROUTER_URL" \
--hugo-dir hugo/public-qa \
--check-islands $SLUGS_ARG

- name: Publish QA content to HANA
# Skip when the ONLY targeted slug failed to fetch (author source error,
# #2384): nothing rendered for it, so a slug-scoped publish would throw
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/rebuild-content.yml
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,10 @@ jobs:
CONTENT_API_KEY: ${{ secrets.CONTENT_API_KEY }}
TUTORIAL_SLUG: ${{ inputs.slug || steps.mode.outputs.dispatch_slug }}
TUTORIAL_SLUGS: ${{ inputs.slugs || steps.mode.outputs.dispatch_slugs }}
# [retired-slug-noop] Lets fetch-tutorials distinguish a human manual
# dispatch (typo → fail-fast) from an auto-dispatched admin-write /
# scheduled run (a retired-slug filter is "nothing to publish", exit 0).
TRIGGER_SOURCE: ${{ inputs.trigger-source }}
# [#208/#275/#312] AI-quiz authoring. Always-on post-#312
# (2026-06-27 — soak window completed clean). fetch-tutorials.ts
# wakes the AI generator on tutorials carrying [AUTOAUTHOR_*]
Expand Down Expand Up @@ -809,6 +813,21 @@ jobs:
env:
CAP_BASE_URL: ${{ steps.srv.outputs.srv_url }}

# Generated CSS (#2896 follow-up). sap-fundamental.css (build:css, postcss)
# and fundamental-styles-icon.css (build:icon-subset) are build outputs, no
# longer committed — they are .gitignored. This workflow drives /tmp/hugo
# directly rather than `npm run build:all`, so — exactly like the feed
# fetchers above — it must regenerate them explicitly BEFORE the Hugo build,
# or Hugo fingerprints a missing/stale file and the published content
# references a /css/<hash>.css the deployed approuter doesn't serve (the
# approuter-asset guard then blocks publish). build:css + build:icon-subset
# produce byte-identical output to the deploy's build:all, so both publish
# paths agree on the fingerprint.
- name: Build fingerprinted CSS (sap-fundamental + icon subset)
run: |
npm run build:icon-subset
npm run build:css

- name: Build Hugo site
run: /tmp/hugo --source hugo --minify

Expand Down
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,16 @@ test/a11y/axe-results.md
srv-qa/lib/parsers.bundle.mjs
.scratch/

# Generated CSS — build outputs, NOT hand-edited sources (#2896 follow-up).
# sap-fundamental.css is emitted by `build:css` (postcss from sap-fundamental.src.css);
# fundamental-styles-icon.css by `build:icon-subset` (scripts/build-icon-subset.cjs).
# Committing them let the generated bytes drift from source: a deploy regenerates
# them (new fingerprint) while rebuild-content.yml used the stale committed copy
# (old fingerprint) → the approuter-asset guard blocked publish. Both build paths
# now regenerate from source; the outputs must never be committed again.
hugo/assets/css/sap-fundamental.css
hugo/assets/css/fundamental-styles-icon.css

# VitePress documentation site
docs/.vitepress/dist/
docs/.vitepress/cache/
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ The load-bearing few. **Full detail for every relocated item → [tutorials-ims-

- **Fresh worktree: run `npm run setup` after `npm install`** — global npmrc `ignore-scripts=true` skips native builds; without it `hugo-apps/node_modules` is empty and `better-sqlite3` won't build (tests hang / fail resolving `@mediapipe/tasks-vision`).
- **`ignore-scripts=true` silences all `pre*`/`post*` hooks** — local `build:all` does NOT fire `postbuild:apps`; island-manifest + `build:page-fallback` are wired as explicit `build:all` steps. Rely on the hook and merged JS/CSS ships dead (unhashed island paths) → "not deployed" despite a green deploy. → gotchas.md "Build artifacts & lifecycle hooks".
- **Deploy refuses a dirty Hugo working tree (#2886)** — uncommitted edits under `hugo/assets/**` or `hugo/layouts/**` make Hugo fingerprint dirty CSS/JS, so the approuter serves a hash the committed-source content pipeline never bakes → 404 on every tutorial (the 2026-10-21 PROD-down). `deploy-mta.cjs` **Step 0.5** `die(1)`s on any tracked-modified build input (branch/SHA guards miss it — a dirty tree keeps the same HEAD SHA). Fix: commit or `git stash push -- <file>`. → gotchas.md "Deploy bakes dirty working-tree CSS".
- **`hugo/content/tutorials/` is entirely generated** — never edit; `fetch-tutorials` overwrites. Edit `scripts/parsers/` or source repos.
- **Group/Mission completions are rollup-derived (#1934)** — `srv/lib/completion-rollup.js` recomputes parents after any TUTORIAL/PUZZLE/CHECKPOINT/PETOBERFEST completion; upserts on `(user_ID, taskLegacyId, taskType)`; NOT a `content-store.js` dep. → gotchas.md "Completions rollup".
- **Never run `publish-content` from a workstation** — use `gh workflow run rebuild-content.yml`; workstation publishes skip CI validation.
Expand Down
8 changes: 8 additions & 0 deletions app/admin/pats/webapp/ext/MintDialog.fragment.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,15 @@
placeholder="{i18n>mint.fieldScopesPlaceholder}">
<core:Item key="read" text="read" />
<core:Item key="write" text="write" />
<!-- #2857: `admin` scope — hidden by default; onMintToken makes it
visible ONLY when PatService.patCapabilities().adminScopeAvailable
is true (flag ON AND caller holds the Tutorials Admin role). A
non-admin never sees it, so can't mint a PAT that would 403. -->
<core:Item id="patMintScopeAdmin" key="admin" text="admin" visible="false" />
</MultiComboBox>
<!-- #2857: explains the admin scope; shown only when the admin item is. -->
<Label text="" />
<Text id="patMintAdminHint" text="{i18n>mint.adminScopeHint}" visible="false" />

<Label text="{i18n>mint.fieldTtl}" />
<StepInput
Expand Down
57 changes: 45 additions & 12 deletions app/admin/pats/webapp/ext/PatActionsController.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ sap.ui.define([
let _resolvedView = null;
let _mintDialog = null;
let _mintedDialog = null;
// #2857: cache the admin-scope capability so repeated Mint presses don't
// re-probe the backend. null = not yet resolved.
let _adminScopeAvailable = null;

function _initialMintState() {
return { name: "", scopes: ["read"], ttlDays: 90 };
Expand Down Expand Up @@ -115,6 +118,31 @@ sap.ui.define([
if (lr && lr.getBinding("items")) lr.getBinding("items").refresh();
}

// #2857: resolve whether the caller may mint an admin-scoped PAT, via the
// unbound PatService.patCapabilities function. Cached across presses. On any
// error, fail CLOSED (treat as unavailable) so the admin item stays hidden.
function _resolveAdminScope(view) {
if (_adminScopeAvailable !== null) return Promise.resolve(_adminScopeAvailable);
const op = view.getModel().bindContext("/patCapabilities(...)");
return op.execute().then(function () {
const result = op.getBoundContext().getObject() || {};
_adminScopeAvailable = !!result.adminScopeAvailable;
return _adminScopeAvailable;
}).catch(function () {
_adminScopeAvailable = false;
return false;
});
}

// Show/hide the `admin` MultiComboBox item. The item is declared in the
// fragment (visible=false), so it is scoped under the fragment's owner id.
function _applyAdminScopeVisibility(view, available) {
const item = Fragment.byId(view.getId(), "patMintScopeAdmin");
if (item) item.setVisible(!!available);
const hint = Fragment.byId(view.getId(), "patMintAdminHint");
if (hint) hint.setVisible(!!available);
}

const handlers = {

// Toolbar button press. `arg` is a UI5 Event (sap.m.Button) or a context
Expand All @@ -132,18 +160,23 @@ sap.ui.define([
} else {
model.setData(_initialMintState());
}
if (_mintDialog) {
_mintDialog.open();
return;
}
Fragment.load({
id: view.getId(),
name: "sap.tutorials.admin.pats.ext.MintDialog",
controller: handlers
}).then(function (dlg) {
_mintDialog = dlg;
view.addDependent(dlg);
dlg.open();
// #2857: probe admin-scope capability, then show/hide the admin item.
_resolveAdminScope(view).then(function (available) {
if (_mintDialog) {
_applyAdminScopeVisibility(view, available);
_mintDialog.open();
return;
}
Fragment.load({
id: view.getId(),
name: "sap.tutorials.admin.pats.ext.MintDialog",
controller: handlers
}).then(function (dlg) {
_mintDialog = dlg;
view.addDependent(dlg);
_applyAdminScopeVisibility(view, available);
dlg.open();
});
});
},

Expand Down
2 changes: 2 additions & 0 deletions app/admin/pats/webapp/i18n/i18n.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ mint.fieldNamePlaceholder=e.g. claude-desktop
mint.fieldScopes=Scopes
#XFLD: Scopes field placeholder
mint.fieldScopesPlaceholder=Select one or more scopes
#YMSG: Hint shown when the admin scope is available (#2857)
mint.adminScopeHint=The admin scope grants headless access to the admin OData (/admin-pat/*) and GraphQL (/graphql-pat) surfaces. Admin tokens are capped at 90 days.
#XFLD: TTL field label
mint.fieldTtl=Validity
#XFLD: TTL unit suffix
Expand Down
1 change: 0 additions & 1 deletion app/scanner/webapp/Component.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ sap.ui.define([

init: function () {
UIComponent.prototype.init.apply(this, arguments);
this.getRouter().initialize();
var oDeviceModel = new JSONModel(Device);
oDeviceModel.setDefaultBindingMode("OneWay");
this.setModel(oDeviceModel, "device");
Expand Down
4 changes: 4 additions & 0 deletions app/scanner/webapp/controller/App.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ sap.ui.define([
var CONTESTANT_DEFAULTS = {
uid: null,
eventId: null,
lastName: "",
firstInitial: "",
claimedCount: 0,
tutorialsCompleted: "",
groupsCompleted: "",
Expand Down Expand Up @@ -42,6 +44,8 @@ sap.ui.define([
oContext.setParameter("eventId", eventId);
await oContext.execute();
var result = oContext.getBoundContext().getObject();
this._oContestantModel.setProperty("/lastName", result.lastName || "");
this._oContestantModel.setProperty("/firstInitial", result.firstInitial || "");
this._oContestantModel.setProperty("/tutorialsCompleted", result.tutorialsCompleted || "");
this._oContestantModel.setProperty("/groupsCompleted", result.groupsCompleted || "");
this._oContestantModel.setProperty("/missionsCompleted", result.missionsCompleted || "");
Expand Down
25 changes: 0 additions & 25 deletions app/scanner/webapp/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,31 +54,6 @@
}
]
},
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"async": true,
"viewPath": "sap.tutorials.scanner.view",
"controlAggregation": "pages",
"controlId": "app",
"clearControlAggregation": false
},
"routes": [
{
"name": "main",
"pattern": ":?query:",
"target": ["main"]
}
],
"targets": {
"main": {
"viewType": "XML",
"viewId": "App",
"viewName": "App"
}
}
},
"rootView": {
"viewName": "sap.tutorials.scanner.view.App",
"type": "XML",
Expand Down
28 changes: 16 additions & 12 deletions app/scanner/webapp/view/App.view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<content>
<!-- Idle state: shown when no scan result is present -->
<VBox id="vboxIdle"
visible="{= !${contestant>loaded} &amp;&amp; !${contestant>loading} }"
visible="{= !${contestant>/loaded} &amp;&amp; !${contestant>/loading} }"
class="sapUiLargeMarginTop sapUiMediumMarginBottom">
<VBox id="vboxIdleContent" alignItems="Center" class="sapUiMediumMarginBottom">
<core:Icon
Expand Down Expand Up @@ -36,14 +36,18 @@
<!-- Data panel: shown while loading and after load -->
<Panel
id="contestantCard"
visible="{= ${contestant>loaded} || ${contestant>loading} }"
busy="{contestant>loading}"
visible="{= ${contestant>/loaded} || ${contestant>/loading} }"
busy="{contestant>/loading}"
class="sapUiTinyMarginTop">
<content>
<!-- Identity header -->
<!-- Identity header — "LastName, F." for eyeball match against
the badge; the scanned UID shown small underneath. -->
<VBox id="vboxIdentity" class="sapUiSmallMarginBeginEnd sapUiSmallMarginTopBottom">
<Title id="resultUid" text="{contestant>uid}" level="H5"
class="sapUiTinyMarginBottom" />
<Title id="resultName"
text="{= ${contestant>/lastName} ? (${contestant>/lastName} + ', ' + ${contestant>/firstInitial} + '.') : ${contestant>/uid} }"
level="H5" class="sapUiTinyMarginBottom" />
<Text id="resultUid" text="{contestant>/uid}"
visible="{= !!${contestant>/lastName} }" />
</VBox>

<!-- Prizes Claimed — highlighted row -->
Expand All @@ -52,31 +56,31 @@
justifyContent="SpaceBetween"
alignItems="Center">
<Label id="lblPrizesClaimed" text="Prizes Claimed" design="Bold" />
<Text id="prizeCount" text="{= ${contestant>claimedCount} + ' of 2' }" />
<Text id="prizeCount" text="{= ${contestant>/claimedCount} + ' of 2' }" />
</HBox>

<!-- Remaining stats as a list -->
<List id="listStats" showSeparators="Inner">
<DisplayListItem
id="statTutorials"
label="Tutorials Completed"
value="{contestant>tutorialsCompleted}" />
value="{contestant>/tutorialsCompleted}" />
<DisplayListItem
id="statGroups"
label="Groups Completed"
value="{contestant>groupsCompleted}" />
value="{contestant>/groupsCompleted}" />
<DisplayListItem
id="statMissions"
label="Missions Completed"
value="{contestant>missionsCompleted}" />
value="{contestant>/missionsCompleted}" />
</List>
</content>
</Panel>
</content>

<footer>
<OverflowToolbar id="footerToolbar"
visible="{contestant>loaded}">
visible="{contestant>/loaded}">
<Button
id="btnScanAgain"
text="Scan Again"
Expand All @@ -89,7 +93,7 @@
icon="sap-icon://gift"
type="Emphasized"
press="onClaimPrize"
enabled="{= ${contestant>loaded} &amp;&amp; ${contestant>claimedCount} &lt; 2 }" />
enabled="{= ${contestant>/loaded} &amp;&amp; ${contestant>/claimedCount} &lt; 2 }" />
</OverflowToolbar>
</footer>

Expand Down
Loading
Loading