feat(reports): 'Build your Logic Model' empty-state on the report LM page#3682
feat(reports): 'Build your Logic Model' empty-state on the report LM page#3682gloriafolaron wants to merge 6 commits into
Conversation
…page When a strategy has no Logic Model canvas, the report's Logic Model page showed a bare one-line string. Existing users who never built a logic model (but whose report depends on it) got a dead end. Replace it with an inviting empty-state: what a logic model unlocks + a 'Build a Logic Model' CTA to /logicmodelcanvas/showCanvas (the strategy's canvas, which shows the 'Start your Logic Model' 3-door screen incl. the reverse 'build from what's already here' flow). CTA is strategy-scoped; program scope keeps the explanatory copy only. Note: only reachable once a user already has a type='strategy' project — the upstream gap (flat-project users can't reach the strategy report at all) is separate and needs a product decision. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85
There was a problem hiding this comment.
Pull request overview
Adds a more helpful empty-state experience to the Stakeholder Report “Logic Model” page so users without an authored Logic Model canvas aren’t left at a dead end, and (when in strategy scope) are guided to the Logic Model builder entry point.
Changes:
- Replaces the prior one-line “no canvas” message with a styled empty-state panel including title/body copy and iconography.
- Adds a strategy-scoped “Build a Logic Model” CTA linking to
/logicmodelcanvas/showCanvas(program scope shows copy without the CTA). - Introduces 4 new translation strings for the empty-state content.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| app/Domain/Reports/Templates/partials/stakeholder/page-lm.blade.php | Renders the new empty-state UI and strategy-only CTA when $hasLM is false. |
| app/Language/en-US.ini | Adds new stakeholder.lm.empty_* translation keys used by the empty-state. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| </style> | ||
| <div class="p2-wrap"> | ||
| <div class="p2-lm-empty"> | ||
| <span class="ic"><i class="fa fa-diagram-project"></i></span> |
| <style> | ||
| .rd-scope .p2-lm-empty{max-width:560px;margin:24px auto;text-align:center;padding:38px 28px;background:var(--rd-panel);border:1px solid var(--rd-line);border-radius:var(--rd-r-sm);box-shadow:var(--rd-sh-sm);} |
…g board Validation surfaced that StrategyReport gates logicModel on board EXISTENCE, not item count — so a blank Logic Model board (0 items) makes $hasLM true and renders a hollow skeleton (columns, empty stages, no prompt) instead of the empty-state. The reverse flow creates a board on start, so that hollow state is reachable. Key the empty-state on item count instead: compute $lmHasContent (any stage carries items) and show the 'Build your Logic Model' populate-from-your-work CTA whenever the model is empty — whether no board exists or a board exists with nothing in it. Unifies the three states (no board / blank board / filled) into one coherent 'here's how to populate it from what you already have' story. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
app/Domain/Reports/Templates/partials/stakeholder/page-lm.blade.php:178
- Decorative Font Awesome icons should be hidden from assistive tech; otherwise some screen readers may announce them. Add aria-hidden="true" to the icon element.
<span class="ic"><i class="fa fa-diagram-project"></i></span>
app/Domain/Reports/Templates/partials/stakeholder/page-lm.blade.php:183
- Decorative icon inside the CTA should be aria-hidden so the link text is the accessible name.
<i class="fa fa-wand-magic-sparkles"></i> {{ __('stakeholder.lm.empty_cta') }}
| stakeholder.lm.no_canvas = "No Logic Model canvas authored yet. Build one on the Board to see it read out here." | ||
| stakeholder.lm.empty_title = "Build your Logic Model" | ||
| stakeholder.lm.empty_body = "A Logic Model connects your programs, goals, and milestones into one story — what you do, what changes, and why it matters. It powers this page and the coverage read-out." | ||
| stakeholder.lm.empty_cta = "Build a Logic Model" | ||
| stakeholder.lm.empty_hint = "We'll scan the work already in this strategy and suggest what maps to each stage — you pick what fits." |
|
Status: triaged / needs visual review · Priority: P3 (feature UX, item-count gate is the one logic risk) · Next action: eyeball the no-board + blank-board empty-states; confirm the Automated maintainer first-response (advisory only — not an approval; merge authority stays with @marcelfolaron / @broskees). First review, at open ( 1. Intent: Turns the report's Logic Model page from a dead end into an on-ramp: when the LM is empty it renders an inviting empty-state (what an LM unlocks + a Build a Logic Model CTA → 2. Change requests:
3. Acceptance checklist (must pass before merge):
4. CI status: Advisory only — not an approval, not marking ready, not merging. Merge authority stays with @marcelfolaron / @broskees. |
- Contrast: body/hint used --rd-text-3/-4 (~3:1, fail WCAG AA as body copy); set explicit #54636b / #69767d (5.6:1 / 4.7:1) so the invitation is legible. - Semantics: title is now an <h2>; decorative icons get aria-hidden. - Polish (closer to the onboarding sample): larger softer icon tile w/ depth, 22px heading, generous 52px padding, 600px card, refined CTA with lift + soft shadow, roomier rhythm. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
app/Domain/Reports/Templates/partials/stakeholder/page-lm.blade.php:194
$stagesis recalculated from$logicModel['coverageMatrix']['stages']even though$lmStageswas already derived earlier for the empty-state gate. Reusing$lmStageshere avoids duplicated indexing and guarantees the same stage structure is used both for the content check and for rendering.
@php
$stages = $logicModel['coverageMatrix']['stages'] ?? [];
$columns = $logicModel['coverageMatrix']['columns'] ?? [];
…mpty-state
- Tinted zone behind the card (soft mint gradient) so the white invitation
lifts off the page — closer to the onboarding sample.
- 'We found N programs · M projects in this strategy' line above the CTA,
derived from $programRows (type='program' count + summed projectCount).
Strategy scope only; self-hides when there's no linked work. Makes the
invitation concrete ('we see your existing work'), matching the reverse
flow's 'we found existing work' banner.
- deck passes $programRows into page-lm; 5 new i18n keys.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85
- Roomier chip: 10x20 padding, 13px, wider gaps. - Totals read as totals: bolded counts + a spaced separator span so '3 programs · 12 projects' has breathing room. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85
| foreach ($programRows ?? [] as $lmRow) { | ||
| if (($lmRow['type'] ?? '') === 'program') { | ||
| $lmProgramCount++; | ||
| } | ||
| $lmProjectCount += (int) ($lmRow['projectCount'] ?? 0); | ||
| } |
The 9px-per-side separator margin pushed the two totals too far apart; 5px each side reads as one cohesive '3 programs · 12 projects' unit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
app/Domain/Reports/Templates/partials/stakeholder/page-lm.blade.php:172
- $programRows is treated as an array here, but in other stakeholder report pages it is documented/handled as an object list (e.g., page-programs casts each row to (array)). If $programRows rows are objects, using $lmRow['type'] / ['projectCount'] will fatal ("Cannot use object of type … as array"). Cast each row to array (or handle both shapes) before indexing.
foreach ($programRows ?? [] as $lmRow) {
if (($lmRow['type'] ?? '') === 'program') {
$lmProgramCount++;
}
$lmProjectCount += (int) ($lmRow['projectCount'] ?? 0);
Why
Existing users whose report depends on the Logic Model but who never built (or only started) one hit a dead end on the report's Logic Model page. This turns it into an on-ramp.
What
When the Logic Model is empty, the LM page renders an inviting empty-state: what a logic model unlocks + a Build a Logic Model CTA →
/logicmodelcanvas/showCanvas(the strategy's canvas, which shows the 'Start your Logic Model' 3-door screen including the reverse 'build from what's already here' flow). CTA is strategy-scoped.Keyed on item count, not board existence. A headless validation run found
StrategyReportgateslogicModelon a board existing, not on it having items — so a blank board (0 items, which the reverse flow creates on start) rendered a hollow skeleton instead of the empty-state. Fixed:$lmHasContentchecks whether any stage carries items, unifying three states — no board / blank board / filled — into one coherent 'here's how to populate it from what you already have' story.page-lm.blade.php— empty-state markup + scoped.rd-*styles;$lmHasContentgate.en-US.ini— 4 newstakeholder.lm.empty_*keys.Blade compiles clean; needs a language-cache clear on deploy (automatic on version bump).
It's a design surface — worth eyeballing rendered (both the no-board and blank-board cases) before merge.
Scope note
Serves the v1 target (a user who already has a
type='strategy'project). The separate flat-project→strategy on-ramp is a product decision, not addressed here.🤖 Generated with Claude Code