Skip to content

feat(reports): 'Build your Logic Model' empty-state on the report LM page#3682

Open
gloriafolaron wants to merge 6 commits into
masterfrom
feat/report-lm-empty-state
Open

feat(reports): 'Build your Logic Model' empty-state on the report LM page#3682
gloriafolaron wants to merge 6 commits into
masterfrom
feat/report-lm-empty-state

Conversation

@gloriafolaron

@gloriafolaron gloriafolaron commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

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 StrategyReport gates logicModel on 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: $lmHasContent checks 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; $lmHasContent gate.
  • en-US.ini — 4 new stakeholder.lm.empty_* keys.

Blade compiles clean; needs a language-cache clear on deploy (automatic on version bump).

⚠️ Needs a visual review

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

…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
@gloriafolaron
gloriafolaron requested a review from a team as a code owner July 22, 2026 03:09
@gloriafolaron
gloriafolaron requested review from Copilot and marcelfolaron and removed request for a team July 22, 2026 03:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Comment thread app/Domain/Reports/Templates/partials/stakeholder/page-lm.blade.php Outdated
Comment on lines +147 to +148
<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
Copilot AI review requested due to automatic review settings July 22, 2026 11:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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') }}

Comment thread app/Language/en-US.ini
Comment on lines 3004 to +3008
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."
@marcelfolaron

Copy link
Copy Markdown
Collaborator

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 $lmHasContent gate + language-cache clear on deploy · Owner: @marcelfolaron / @broskees

Automated maintainer first-response (advisory only — not an approval; merge authority stays with @marcelfolaron / @broskees). First review, at open (gloriafolaron, created 2026-07-22).

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 → /logicmodelcanvas/showCanvas, strategy-scoped). The load-bearing fix is that StrategyReport was gating logicModel on a board existing rather than having items, so a blank board (0 items — which the reverse flow's "build from what's here" creates on start) rendered a hollow skeleton; $lmHasContent now checks whether any stage carries items, unifying no-board / blank-board / filled into one coherent story. Two files: page-lm.blade.php (markup + scoped .rd-* styles + the gate) and en-US.ini (4 new keys).

2. Change requests:

  1. ⚠ CR Documentation #1$lmHasContent is the one behavior change; confirm it counts items across all stages, not just the first. The whole bug is a board-exists-but-empty false-positive. Verify the gate returns false when every stage is empty and true as soon as any stage (inputs/activities/outputs/outcomes/impact) has ≥1 item — an off-by-one that only checks outcomes, say, would re-introduce the hollow skeleton for a board with only inputs. This is cheap to lock with a unit test on the StrategyReport gate if one's reachable; if it's pure Blade, note the manual check.
  2. ⚠ CR Gitignore #2 — the "blank board" case is the specific one to eyeball. The PR flags it: a reverse-flow start creates a 0-item board, so confirm that exact path (start reverse flow → land on report LM page before adding anything) shows the empty-state, not the skeleton. This is the regression the PR exists to kill.
  3. ◐ CR Create Email Notification Class (including user settings to opt out) #3 — language-cache clear on deploy. PR notes the 4 new stakeholder.lm.empty_* keys need a language-cache clear (automatic on version bump). Confirm this ships alongside a version bump, or the new keys render as raw stakeholder.lm.empty_* strings in prod until the cache clears.
  4. ◐ CR Create Installation Script/Routine #4 — CTA scope correctness. The CTA is strategy-scoped to /logicmodelcanvas/showCanvas. Confirm it resolves to the current strategy's canvas from the report context (not a global/last-used canvas), so a user on Strategy A's report doesn't get sent to Strategy B's canvas.

3. Acceptance checklist (must pass before merge):

  1. $lmHasContent true iff any stage has ≥1 item, false for all-empty — verified across no-board, blank-board, and filled (CR Documentation #1, CR Gitignore #2).
  2. New i18n keys ship with a version bump / cache clear so they don't render raw in prod (CR Create Email Notification Class (including user settings to opt out) #3); CTA resolves to the current strategy's canvas (CR Create Installation Script/Routine #4).
  3. Blade compiles clean; CI green on this head. Visual review of both empty-state cases (no-board + blank-board) done, per the PR's own "needs a visual review" flag.

4. CI status: ⚠️ Couldn't read check-runs this cycle — confirm green in the UI. It's primarily a design surface, so the visual pass on the two empty-state cases is the real gate; the $lmHasContent logic is the one thing worth a test.

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
Copilot AI review requested due to automatic review settings July 22, 2026 14:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • $stages is recalculated from $logicModel['coverageMatrix']['stages'] even though $lmStages was already derived earlier for the empty-state gate. Reusing $lmStages here 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
Copilot AI review requested due to automatic review settings July 22, 2026 15:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

- 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
Copilot AI review requested due to automatic review settings July 22, 2026 15:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment on lines +168 to +173
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
Copilot AI review requested due to automatic review settings July 22, 2026 15:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants