Skip to content

feat: redesign program rules playground with rule-effect visualization - #12

Merged
nnkogift merged 8 commits into
mainfrom
feature/program-rules-playground-redesign
Aug 2, 2026
Merged

nnkogift merged 8 commits into
mainfrom
feature/program-rules-playground-redesign

Conversation

@nnkogift

@nnkogift nnkogift commented Aug 1, 2026 •

Copy link
Copy Markdown
Owner

Summary

Implements the design in apps/playground/design/Design handoff.md: a redesigned program list, a merged tracker/event program page (context bar + enrollment rail + unified rule panel), and a rule-effect visualization system that shows which program rule hid, required, assigned, warned, or errored on each field/section — including "ghost" placeholders that keep hidden fields/sections in their grid slot instead of collapsing to nothing.

  • Rule provenance: new @dhis2-form-utils/hooks exports (useRuleEffectTrace, useFieldRuleEffect, useSectionRuleEffect) generalize the rule-trace-to-target lookup that previously only lived inside devtools, so any consumer can ask "which rule caused this effect?"
  • Ghost placeholders / badges: new playground RuleAwareField / FormSectionCard call useFieldControl / useSectionState directly instead of D2Field / FormSection (which just return null when hidden), so hidden fields/sections render as dashed-hatch placeholders with the responsible rule's name.
  • Merged devtools panel: ProgramRulesPanel + RuleDevtoolsPanel are replaced by a single RulesPanel (Rules / Trace / Graph tabs) in @dhis2-form-utils/devtools, with scope-aware 4-state rule cards (selected / firing-in-scope / idle-in-scope / out-of-scope).
  • Tracker rail: new EnrollmentRail + TrackerProgramShell render registration and every program stage (including repeatable-stage events) as independently-mounted forms, CSS-hidden rather than unmounted when not selected — so switching between stages/events never discards data already typed into another one.
  • Program list: segmented Tracker/Event filter, stage/rule counts, last-updated column, whole-row navigation.
  • Context bar: org unit and date fields moved out of the form body into a shared ProgramContextBar, so they're playground context rather than part of the rule-evaluation surface.
  • Excludes apps/playground/design/** (design handoff reference material, including a generated design-system export bundle) from eslint, prettier, and git.

Test plan

  • pnpm typecheck — clean across all packages
  • pnpm lint — clean (root config now excludes apps/playground/design/**)
  • pnpm --filter @dhis2-form-utils/hooks|devtools|rules|metadata test — all green (139 tests)
  • Playground Jest tests — 21/21 passing (one pre-existing, unrelated usehooks-ts/Jest ESM suite-load failure, present on main before this change)
  • Live-verified against a real DHIS2 instance: tracker program (registration + all stages + repeatable-stage add-event with independent, persisted form state per event), event-only program (no rail), rule badges/ghost placeholders/feedback attribution, and the merged Rules/Trace/Graph panel's scope + firing counts across slots

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a complete Program Rules Playground for tracker and event programs.
    • Added program search, type filters, metadata, stage and rule counts, and last-updated details.
    • Added enrollment and stage navigation, repeatable event creation, and preserved event dates.
    • Added unified Rules, Trace, and Graph views with rule status, scope, conditions, and fullscreen graph viewing.
    • Added rule-aware fields, hidden-field and section placeholders, effect badges, feedback messages, and ghost visibility controls.
  • Style
    • Refined layouts, spacing, colors, accessibility, and localized labels throughout the playground.
  • Documentation
    • Added design handoff materials describing the playground experience and acceptance criteria.

nnkogift and others added 3 commits July 31, 2026 12:48
Implements the design in apps/playground/design/Design handoff.md:
program list restyle, a merged tracker/event program page (context bar +
enrollment rail + unified rule panel), and a rule-effect visualization
system that shows which program rule hid, required, assigned, warned, or
errored on each field/section, including ghost placeholders for hidden
slots.

- New @dhis2-form-utils/hooks `useRuleEffectTrace`/`useFieldRuleEffect`/
  `useSectionRuleEffect` generalize the trace-to-rule lookup previously
  internal to devtools, so any consumer can attribute a field/section
  effect back to the rule that caused it.
- New playground `RuleAwareField`/`FormSectionCard` call `useFieldControl`/
  `useSectionState` directly instead of `D2Field`/`FormSection`, so hidden
  fields/sections can render as ghost placeholders instead of `null`.
- Merged `ProgramRulesPanel` + `RuleDevtoolsPanel` into a single
  `RulesPanel` (Rules/Trace/Graph tabs) in @dhis2-form-utils/devtools,
  with scope-aware 4-state rule cards (selected/firing/idle/out-of-scope).
- New `EnrollmentRail` + `TrackerProgramShell` render every tracker stage
  and repeatable-stage event as an independently-mounted form (CSS-hidden
  rather than unmounted) so switching slots never discards in-progress
  data.
- Program list: segmented type filter, rule/stage counts, whole-row
  navigation; org unit and date fields moved out of the form into a
  shared `ProgramContextBar`.
- Excludes apps/playground/design/** (reference/design-tool material,
  including a generated design-system export bundle) from eslint,
  prettier, and git.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 1, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@nnkogift, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bf38f151-441d-4dd6-aea0-5573a4b9360b

📥 Commits

Reviewing files that changed from the base of the PR and between 54bdeac and 1f3a1c4.

📒 Files selected for processing (4)
  • .idea/inspectionProfiles/Project_Default.xml
  • .idea/jsLinters/eslint.xml
  • .idea/prettier.xml
  • docs/dev-tools.md
📝 Walkthrough

Walkthrough

The change adds the Program Rules Playground, replaces the program placeholder route, introduces tracker navigation and rule-aware forms, consolidates rule devtools into RulesPanel, and adds supporting design, metadata, translation, and tooling updates.

Changes

Program Rules Playground

Layer / File(s) Summary
Design playground and runtime
apps/playground/design/*
Adds the design handoff, interactive playground documents, and browser runtime.
Program discovery and page entry
apps/playground/src/pages/*, apps/playground/src/components/programs/ProgramList*, apps/playground/src/hooks/usePrograms.ts, apps/playground/src/utils/*
Expands program metadata, normalizes API responses, redesigns filters and table rows, and routes program details through ProgramPage.
Rule state and devtools
components/dhis2-ui/src/fields/*, utils/hooks/src/*, utils/devtools/src/*
Adds rule-effect tracing, stage label lookup, full rule catalogs, unified Rules/Trace/Graph views, and reusable field widget dispatch.
Tracker navigation and rule-aware forms
apps/playground/src/components/programs/*, apps/playground/src/components/programs/forms/*, apps/playground/src/components/rules/*
Adds tracker slot navigation, event draft handling, rule-aware fields, hidden placeholders, feedback rendering, shared context controls, and updated registration and event forms.
Design tooling and metadata support
.gitignore, .prettierignore, eslint.config.js, apps/playground/eslint.config.mjs, apps/playground/i18n/en.pot, packages/config/tailwind-theme.css, packages/metadata/src/*, docs/dev-tools.md, .claude/launch.json
Excludes design artifacts from tooling, adds translations and theme styling, requests repeatable stage metadata, and updates devtools documentation and launch configuration.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ProgramPage
  participant TrackerProgramShell
  participant EnrollmentRail
  participant ProgramStageFormScreen
  participant RulesPanel
  ProgramPage->>TrackerProgramShell: load tracker metadata and selected context
  TrackerProgramShell->>EnrollmentRail: render registration and stage slots
  EnrollmentRail-->>TrackerProgramShell: select stage or create event draft
  TrackerProgramShell->>ProgramStageFormScreen: render selected stage form
  ProgramStageFormScreen->>RulesPanel: render rule catalog, trace, and graph
  RulesPanel-->>ProgramStageFormScreen: display rule effects and selections
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the redesign of the program rules playground and its rule-effect visualization focus.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/program-rules-playground-redesign

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

cursor[bot]
cursor Bot approved these changes Aug 1, 2026 •

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 15

🧹 Nitpick comments (10)
apps/playground/design/Program Rules Playground.dc.html (2)

1037-1037: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the no-op filter.

(s) => (ghosts ? true : s) keeps every section in both branches, because each section object is truthy. Drop the .filter(...) call.

♻️ Proposed fix
-    ].filter((s) => (ghosts ? true : s));
+    ];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/playground/design/Program` Rules Playground.dc.html at line 1037, Remove
the no-op .filter call following the sections array construction, preserving the
array contents and surrounding ghosts logic unchanged.

1060-1060: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Set badgeTitle so the badge tooltip names the rule.

Line 1060 and Line 1242 omit badgeTitle, so title renders as an empty string. The handoff specifies the tooltip text <ACTIONTYPE> from rule: <rule name>, and the sibling variants at Lines 1030-1031 already follow it.

♻️ Proposed fix
-          : field('Referral to clinic', '', { badge: 'hide', badgeText: 'Hidden' }),
+          : field('Referral to clinic', '', { badge: 'hide', badgeText: 'Hidden', badgeTitle: 'HIDEFIELD from rule: Hide referral unless underweight' }),
-          field('Date treatment started', '', { badge: 'warning', badgeText: 'Warning', ring: '`#ffa902`', help: 'Treatment recorded without a start date.', helpFg: '`#6f3205`' }),
+          field('Date treatment started', '', { badge: 'warning', badgeText: 'Warning', badgeTitle: 'SHOWWARNING from rule: Warn on missing treatment date', ring: '`#ffa902`', help: 'Treatment recorded without a start date.', helpFg: '`#6f3205`' }),

Also applies to: 1242-1242

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/playground/design/Program` Rules Playground.dc.html at line 1060, Update
the Referral to clinic rule definitions at the shown locations to include
badgeTitle using the specified “<ACTIONTYPE> from rule: <rule name>” tooltip
format, matching the sibling variants’ implementation and preserving the
existing hidden badge configuration.
apps/playground/design/Design handoff.md (1)

44-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the planned file list with the implemented components.

The plan lists components/rules/RulePanel.tsx and components/rules/RuleCatalogList.tsx inside the playground app. The PR delivers the unified panel as utils/devtools/src/RulesPanel.tsx, and delivers RuleFeedbackList.tsx, FieldEffectBadge.tsx, HiddenFieldPlaceholder.tsx, and HiddenSectionPlaceholder.tsx under apps/playground/src/components/rules/. Update the two panel entries so the handoff matches the shipped layout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/playground/design/Design` handoff.md around lines 44 - 49, Update the
planned panel entries in the handoff file list: replace the playground-local
RulePanel.tsx and RuleCatalogList.tsx paths with the shipped unified panel path
utils/devtools/src/RulesPanel.tsx, while keeping the listed
apps/playground/src/components/rules components aligned with their delivered
locations.
apps/playground/eslint.config.mjs (1)

22-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Narrow the import/named disable to the affected files.

This override applies to every file matched by this config. The stated resolver limitation concerns the dynamic @dhis2/ui-icons re-export. A config-wide override can hide unrelated invalid named imports. Scope the override to affected files or fix the resolver configuration. Keep TypeScript typechecking as the backstop.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/playground/eslint.config.mjs` around lines 22 - 25, Scope the
'import/named' override in the ESLint configuration to only the files importing
the dynamically re-exported `@dhis2/ui-icons` symbols, rather than disabling it
globally. Preserve TypeScript typechecking as the validation backstop and leave
the rule enabled for unrelated files.
apps/playground/src/utils/resolveProgramListItem.ts (1)

14-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add unit tests for resolveProgramListItem.

This is a new pure function with edge-case defaulting logic (missing lastUpdated, missing programStages/programRules). No test file exists for it in this batch. Add unit tests covering: a fully-populated raw item, a raw item missing lastUpdated, and a raw item missing programStages/programRules.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/playground/src/utils/resolveProgramListItem.ts` around lines 14 - 25,
Add unit tests for the pure resolveProgramListItem function covering a fully
populated raw item, a missing lastUpdated defaulting to an empty string, and
missing programStages/programRules defaulting their counts to zero. Assert the
complete mapped Program result for each scenario using the project’s existing
test conventions.
apps/playground/src/utils/formatLastUpdated.ts (1)

7-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add unit tests for formatLastUpdated.

The function has two early-return branches (empty string, invalid date) besides the normal formatting path. No test file exists for it. Add unit tests for a valid ISO date, an empty string, and an invalid date string.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/playground/src/utils/formatLastUpdated.ts` around lines 7 - 18, Add unit
tests for formatLastUpdated covering a valid ISO date with the expected
formatted output, an empty string returning '', and an invalid date string
returning ''. Create the appropriate test file using the project’s existing
testing conventions.
utils/devtools/src/RulesPanel.tsx (2)

345-370: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer a real button over role="button" on the article.

The article carries role="button", which overrides the article semantics and places an h3 inside an interactive control. Several screen readers drop headings from the accessibility tree of a button, so the rule name loses its heading semantics. A nested button that wraps the title, or a button element in place of the article, keeps both the semantics and the keyboard handling that you now implement by hand.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@utils/devtools/src/RulesPanel.tsx` around lines 345 - 370, Replace the
interactive article container with a real button element, removing the role,
tabIndex, and manual onKeyDown handling while preserving the existing onClick
selection behavior and styling. Update the surrounding structure as needed so
the rule name remains exposed as a heading rather than being nested inside a
button; use the rule card markup around the visible h3 as the change point.

158-165: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Memoize graphProps.formValues.

form.getValues() changes each render, so graphProps changes and breaks RuleGraphView memoization. toFlowGraph() depends on formValues, recomputes the layout, updates ReactFlow nodes/edges, and also changes edgeCount/nodeCount in FitViewOnChange; memoize the form values and use a consistent key so the graph only recomputes when values, layout key, or graph data change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@utils/devtools/src/RulesPanel.tsx` around lines 158 - 165, Update the
graphProps construction around form.getValues() to memoize formValues with a
stable dependency/key, and use that same consistent key when memoizing
graphProps so RuleGraphView only receives new props when form values, layout
state, or graph data actually change. Preserve the existing entries, fieldState,
selectedEntryId, highlightRuleId, and labelLookup behavior.
apps/playground/src/components/rules/RuleFeedbackList.tsx (1)

57-58: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

The useMemo never caches, and the label lookup is built twice.

Both callers pass a new object literal for metadata. See ProgramEventForm.tsx lines 124-130 and ProgramRegistrationForm.tsx. The dependency therefore changes on every render, so createLabelLookup runs on every render.

The same callers already build an identical lookup and supply it through RuleDisplayProvider. Consume that lookup with useRuleDisplay() and drop the metadata prop. This removes the duplicate construction and the ineffective memo.

♻️ Proposed refactor
-type RuleFeedbackListProps = {
-    metadata: RuleDevtoolsMetadata
-}
-
-export function RuleFeedbackList({ metadata }: RuleFeedbackListProps) {
-    const labelLookup = useMemo(() => createLabelLookup(metadata), [metadata])
+export function RuleFeedbackList() {
+    const { labelLookup } = useRuleDisplay()
     const feedback = useFormFeedback()

Callers then render <RuleFeedbackList /> inside the existing RuleDisplayProvider.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/playground/src/components/rules/RuleFeedbackList.tsx` around lines 57 -
58, Update RuleFeedbackList to remove the metadata prop and the ineffective
useMemo/createLabelLookup construction, and consume the existing label lookup
via useRuleDisplay(). Update its callers to render RuleFeedbackList within the
existing RuleDisplayProvider while preserving current display behavior.
apps/playground/src/components/programs/forms/TrackerProgramShell.tsx (1)

60-72: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Mount stage slots only after their first visit.

Every non-repeatable stage renders immediately. Each slot mounts a ProgramEventForm, which creates its own form store, rule evaluation loop, and RulesPanel. A program with many stages therefore pays the full cost for stages the user never opens.

State preservation only requires that a slot stays mounted after the user opens it. Track the visited slot keys and filter renderableStageSlots by that set.

♻️ Proposed refactor
+    const visitedKeys = useRef<Set<string>>(new Set())
+
     const renderableStageSlots = useMemo<RenderableStageSlot[]>(() => {
         const slots: RenderableStageSlot[] = []
         for (const stage of program.programStages ?? []) {
             if (stage.repeatable) {
                 for (const eventLocalId of eventDraftsByStage[stage.id] ?? []) {
                     slots.push({ stageId: stage.id, eventLocalId })
                 }
             } else {
                 slots.push({ stageId: stage.id, eventLocalId: 'primary' })
             }
         }
         return slots
     }, [program.programStages, eventDraftsByStage])

Then filter in the render body:

if (isSelected) {
    visitedKeys.current.add(key)
}
if (!visitedKeys.current.has(key)) {
    return null
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/playground/src/components/programs/forms/TrackerProgramShell.tsx` around
lines 60 - 72, Track visited stage-slot keys in TrackerProgramShell using a
persistent ref, mark the selected slot as visited during rendering, and filter
unvisited entries before mounting their ProgramEventForm. Preserve mounted slots
after first visit so their state remains intact, while preventing non-selected
stages from mounting initially; update the renderableStageSlots/render loop
rather than changing slot generation semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/playground/design/Program` Rules Playground App.dc.html:
- Around line 467-469: Update the residence field branch near the hidden
registration row so it returns a badge-only field when ghosts are disabled
instead of null. Mirror the existing badge-only behavior used by the
corresponding stageSections and eventSections branches, preserving the current
ghost rendering when g is enabled and keeping the hide badge and label visible
otherwise.

In `@apps/playground/src/components/programs/EnrollmentRail.tsx`:
- Around line 39-52: Add an aria-current attribute to the role="button" element
in EnrollmentRail, deriving its value from the selected state so assistive
technology identifies the currently selected row while preserving the existing
interaction and styling.

In `@apps/playground/src/components/programs/forms/ProgramRegistrationForm.tsx`:
- Around line 149-151: Update RuleFeedbackList and its usage in
ProgramRegistrationForm to consume the existing labelLookup from
RuleDisplayProvider context instead of receiving a newly created, incomplete
metadata object. Ensure stage names resolve through the lookup that includes
programStages, and remove the duplicate metadata-based lookup construction and
per-render object recreation.

In `@apps/playground/src/components/programs/ProgramContextBar.tsx`:
- Around line 59-74: Update the selected prop in the SingleSelectField within
ProgramContextBar to pass undefined when orgUnitId is empty, while preserving
valid org-unit IDs as selected values.

In `@apps/playground/src/components/rules/HiddenSectionPlaceholder.tsx`:
- Around line 18-21: Update the translation rendering in
HiddenSectionPlaceholder so the complete “Section hidden by” sentence and
ruleName are handled as one translation unit, using react-i18next’s Trans
component if needed to preserve the bold styling of ruleName; remove the
fragment translation and adjacent string concatenation.

In `@apps/playground/src/components/rules/RuleAwareField.tsx`:
- Around line 28-57: Update RuleAwareField to preserve the field’s accessible
name when rendering D2FieldWidget: assign or reuse a stable input id, connect
the wrapper label span to that id via the appropriate label association, and
pass the original label and id through widgetControl instead of blanking
fieldConfig.label. Ensure the association also works for D2TextField,
D2LongTextField, D2DateField, and other widgets that forward fieldConfig values
to form controls.

In `@apps/playground/src/components/rules/RuleFeedbackList.tsx`:
- Around line 40-43: Update the feedback row rendering around item.content and
item.value to branch on item.type: render text-only feedback without the
trailing colon or value, while preserving the existing “content: value” layout
for non-text items.

In `@apps/playground/src/pages/ProgramPage.tsx`:
- Around line 38-39: Update handleResetPlayground and the rendered
TrackerProgramShell/ProgramStageFormScreen flow to use a reset counter that
increments on each reset and is passed as the component key, forcing a full
remount. Preserve the existing orgUnitId and primaryDate clearing while ensuring
internal stage, event, draft, and form-hook state is discarded.
- Around line 68-82: Update the error-rendering branch in ProgramPage around
useEventProgramMetadataQuery so it distinguishes a missing program from other
query failures. Use the query’s exposed error details or status to render
“Program not found” only for a confirmed not-found response, and show an
appropriate generic error message for network or server failures while
preserving the existing navigation and error layout.

In `@components/dhis2-ui/src/fields/D2Field.tsx`:
- Around line 27-29: Replace the duplicate D2FieldWidgetProps declaration in
D2Field.tsx with the canonical WidgetProps type imported from
`@dhis2-form-utils/hooks`, and update any references or exports to use that shared
type while preserving the existing control prop contract.

In `@utils/devtools/src/index.ts`:
- Around line 1-20: Update residual references to the removed ProgramRulesPanel
and RuleDevtoolsPanel symbols in CLAUDE.md, docs/dev-tools.md, and the
playground design documentation. Replace them with the current RulesPanel and
RuleDevtoolsScope APIs where applicable, or clearly mark historical references
as replaced content; do not alter the exports in index.ts.

In `@utils/devtools/src/RulesPanel.tsx`:
- Around line 327-333: Update the empty-state message in the RulesPanel catalog
check to state that the program has no rules, removing the “current form
context” wording that implies filtering. Keep the existing translate call and
rendering structure unchanged.
- Around line 286-293: Update the conditional rendering around the screen-reader
summary in RulesPanel so it renders when entries exist rather than only when
entries.length is zero, allowing the evaluations and observedRuleCount values to
be announced. Preserve the tab !== 'rules' condition and add aria-live to the
summary paragraph so updated counts are announced.
- Around line 81-85: Move tab label translation out of the module-level
PANEL_TABS constant so labels resolve during RulesPanel rendering. Preserve the
tab keys in a static PANEL_TAB_KEYS collection, then map over those keys in the
tab list and call resolveTabLabel(key) for each tab, ensuring locale updates are
reflected without changing tab order or behavior.

In `@utils/hooks/src/useRuleEffectTrace.test.tsx`:
- Around line 74-79: Hoist the metadataWithRules() fixture to a stable
module-level reference and use that reference in useHarness’s useTrackerForm
options.metadata. Keep the existing form and formStore return behavior unchanged
so renders do not trigger repeated schema or rule-engine reinitialization.

---

Nitpick comments:
In `@apps/playground/design/Design` handoff.md:
- Around line 44-49: Update the planned panel entries in the handoff file list:
replace the playground-local RulePanel.tsx and RuleCatalogList.tsx paths with
the shipped unified panel path utils/devtools/src/RulesPanel.tsx, while keeping
the listed apps/playground/src/components/rules components aligned with their
delivered locations.

In `@apps/playground/design/Program` Rules Playground.dc.html:
- Line 1037: Remove the no-op .filter call following the sections array
construction, preserving the array contents and surrounding ghosts logic
unchanged.
- Line 1060: Update the Referral to clinic rule definitions at the shown
locations to include badgeTitle using the specified “<ACTIONTYPE> from rule:
<rule name>” tooltip format, matching the sibling variants’ implementation and
preserving the existing hidden badge configuration.

In `@apps/playground/eslint.config.mjs`:
- Around line 22-25: Scope the 'import/named' override in the ESLint
configuration to only the files importing the dynamically re-exported
`@dhis2/ui-icons` symbols, rather than disabling it globally. Preserve TypeScript
typechecking as the validation backstop and leave the rule enabled for unrelated
files.

In `@apps/playground/src/components/programs/forms/TrackerProgramShell.tsx`:
- Around line 60-72: Track visited stage-slot keys in TrackerProgramShell using
a persistent ref, mark the selected slot as visited during rendering, and filter
unvisited entries before mounting their ProgramEventForm. Preserve mounted slots
after first visit so their state remains intact, while preventing non-selected
stages from mounting initially; update the renderableStageSlots/render loop
rather than changing slot generation semantics.

In `@apps/playground/src/components/rules/RuleFeedbackList.tsx`:
- Around line 57-58: Update RuleFeedbackList to remove the metadata prop and the
ineffective useMemo/createLabelLookup construction, and consume the existing
label lookup via useRuleDisplay(). Update its callers to render RuleFeedbackList
within the existing RuleDisplayProvider while preserving current display
behavior.

In `@apps/playground/src/utils/formatLastUpdated.ts`:
- Around line 7-18: Add unit tests for formatLastUpdated covering a valid ISO
date with the expected formatted output, an empty string returning '', and an
invalid date string returning ''. Create the appropriate test file using the
project’s existing testing conventions.

In `@apps/playground/src/utils/resolveProgramListItem.ts`:
- Around line 14-25: Add unit tests for the pure resolveProgramListItem function
covering a fully populated raw item, a missing lastUpdated defaulting to an
empty string, and missing programStages/programRules defaulting their counts to
zero. Assert the complete mapped Program result for each scenario using the
project’s existing test conventions.

In `@utils/devtools/src/RulesPanel.tsx`:
- Around line 345-370: Replace the interactive article container with a real
button element, removing the role, tabIndex, and manual onKeyDown handling while
preserving the existing onClick selection behavior and styling. Update the
surrounding structure as needed so the rule name remains exposed as a heading
rather than being nested inside a button; use the rule card markup around the
visible h3 as the change point.
- Around line 158-165: Update the graphProps construction around
form.getValues() to memoize formValues with a stable dependency/key, and use
that same consistent key when memoizing graphProps so RuleGraphView only
receives new props when form values, layout state, or graph data actually
change. Preserve the existing entries, fieldState, selectedEntryId,
highlightRuleId, and labelLookup behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1f46f5b0-a5e4-47df-8f5b-2cfe3d49b027

📥 Commits

Reviewing files that changed from the base of the PR and between 9d40e13 and b1454da.

📒 Files selected for processing (64)
  • .gitignore
  • .prettierignore
  • apps/playground/design/Design handoff.md
  • apps/playground/design/Program Rules Playground App.dc.html
  • apps/playground/design/Program Rules Playground.dc.html
  • apps/playground/design/github.md
  • apps/playground/design/support.js
  • apps/playground/eslint.config.mjs
  • apps/playground/i18n/en.pot
  • apps/playground/src/AppWrapper.tsx
  • apps/playground/src/components/programs/EnrollmentRail.tsx
  • apps/playground/src/components/programs/ProgramContextBar.tsx
  • apps/playground/src/components/programs/ProgramListFilters.tsx
  • apps/playground/src/components/programs/ProgramListTable.test.tsx
  • apps/playground/src/components/programs/ProgramListTable.tsx
  • apps/playground/src/components/programs/forms/EventFormFields.tsx
  • apps/playground/src/components/programs/forms/EventSystemFields.tsx
  • apps/playground/src/components/programs/forms/FormSectionCard.tsx
  • apps/playground/src/components/programs/forms/OrganisationUnitSelectField.tsx
  • apps/playground/src/components/programs/forms/ProgramEventForm.tsx
  • apps/playground/src/components/programs/forms/ProgramRegistrationForm.tsx
  • apps/playground/src/components/programs/forms/ProgramRegistrationFormScreen.tsx
  • apps/playground/src/components/programs/forms/ProgramStageFormScreen.tsx
  • apps/playground/src/components/programs/forms/RegistrationFormFields.tsx
  • apps/playground/src/components/programs/forms/TrackerProgramShell.tsx
  • apps/playground/src/components/programs/forms/TrackerSystemFields.tsx
  • apps/playground/src/components/programs/trackerSlot.ts
  • apps/playground/src/components/rules/FieldEffectBadge.tsx
  • apps/playground/src/components/rules/GhostToggleButton.tsx
  • apps/playground/src/components/rules/HiddenFieldPlaceholder.tsx
  • apps/playground/src/components/rules/HiddenSectionPlaceholder.tsx
  • apps/playground/src/components/rules/RuleAwareField.tsx
  • apps/playground/src/components/rules/RuleDisplayContext.tsx
  • apps/playground/src/components/rules/RuleFeedbackList.tsx
  • apps/playground/src/components/rules/ghostBackground.ts
  • apps/playground/src/hooks/useProgramHeader.ts
  • apps/playground/src/hooks/usePrograms.ts
  • apps/playground/src/index.css
  • apps/playground/src/pages/ProgramListPage.tsx
  • apps/playground/src/pages/ProgramPage.test.tsx
  • apps/playground/src/pages/ProgramPage.tsx
  • apps/playground/src/pages/ProgramPlaceholderPage.tsx
  • apps/playground/src/types/program.ts
  • apps/playground/src/utils/formatLastUpdated.ts
  • apps/playground/src/utils/formatProgramType.ts
  • apps/playground/src/utils/resolveProgramListItem.ts
  • components/dhis2-ui/src/fields/D2Field.tsx
  • components/dhis2-ui/src/index.ts
  • eslint.config.js
  • packages/config/tailwind-theme.css
  • packages/metadata/src/fieldFilters.ts
  • packages/metadata/src/queries/fields.const.test.ts
  • utils/devtools/src/ProgramRulesPanel.tsx
  • utils/devtools/src/RuleDevtoolsPanel.tsx
  • utils/devtools/src/RulesPanel.tsx
  • utils/devtools/src/buildGraph.test.ts
  • utils/devtools/src/createLabelLookup.test.ts
  • utils/devtools/src/createLabelLookup.ts
  • utils/devtools/src/formatRuleActionSummary.test.ts
  • utils/devtools/src/index.ts
  • utils/devtools/src/resolveProgramRulesList.ts
  • utils/hooks/src/index.ts
  • utils/hooks/src/useRuleEffectTrace.test.tsx
  • utils/hooks/src/useRuleEffectTrace.ts
💤 Files with no reviewable changes (8)
  • apps/playground/src/components/programs/forms/TrackerSystemFields.tsx
  • apps/playground/src/components/programs/forms/EventSystemFields.tsx
  • apps/playground/src/utils/formatProgramType.ts
  • utils/devtools/src/ProgramRulesPanel.tsx
  • apps/playground/src/pages/ProgramPlaceholderPage.tsx
  • utils/devtools/src/RuleDevtoolsPanel.tsx
  • apps/playground/src/hooks/useProgramHeader.ts
  • apps/playground/src/components/programs/forms/OrganisationUnitSelectField.tsx

Comment on lines +467 to +469
g ? field('Residence', '', { ghost: true, ghostNote: 'Hidden by Hide residence for infants', badge: 'hide', badgeText: 'Hidden', badgeTitle: 'HIDEFIELD from rule: Hide residence for infants' }) : null,
field('Mother\u2019s name', '', { badge: 'mandatory', badgeText: 'Required by rule', badgeTitle: 'SETMANDATORYFIELD from rule: Require mother\u2019s name for newborns', ring: '#ffa902', help: 'Required while the child is under 2 months old.', helpFg: '#6f3205' }),
].filter(Boolean) },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep the hidden registration field visible as a badge-only row when ghosts are off.

Line 467 returns null when g is false, and the .filter(Boolean) then removes the row completely. The label and the hide badge disappear with it. stageSections (Lines 493-494) and eventSections (Lines 509-510) keep a badge-only field in the same situation, and Design handoff.md Line 205 specifies the badge on the label as the remaining trace. Mirror the stage branch here.

♻️ Proposed fix
-        g ? field('Residence', '', { ghost: true, ghostNote: 'Hidden by Hide residence for infants', badge: 'hide', badgeText: 'Hidden', badgeTitle: 'HIDEFIELD from rule: Hide residence for infants' }) : null,
+        g ? field('Residence', '', { ghost: true, ghostNote: 'Hidden by Hide residence for infants', badge: 'hide', badgeText: 'Hidden', badgeTitle: 'HIDEFIELD from rule: Hide residence for infants' })
+          : field('Residence', '', { badge: 'hide', badgeText: 'Hidden', badgeTitle: 'HIDEFIELD from rule: Hide residence for infants' }),
         field('Mother\u2019s name', '', { badge: 'mandatory', badgeText: 'Required by rule', badgeTitle: 'SETMANDATORYFIELD from rule: Require mother\u2019s name for newborns', ring: '`#ffa902`', help: 'Required while the child is under 2 months old.', helpFg: '`#6f3205`' }),
-      ].filter(Boolean) },
+      ] },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
g ? field('Residence', '', { ghost: true, ghostNote: 'Hidden by Hide residence for infants', badge: 'hide', badgeText: 'Hidden', badgeTitle: 'HIDEFIELD from rule: Hide residence for infants' }) : null,
field('Mother\u2019s name', '', { badge: 'mandatory', badgeText: 'Required by rule', badgeTitle: 'SETMANDATORYFIELD from rule: Require mother\u2019s name for newborns', ring: '#ffa902', help: 'Required while the child is under 2 months old.', helpFg: '#6f3205' }),
].filter(Boolean) },
g ? field('Residence', '', { ghost: true, ghostNote: 'Hidden by Hide residence for infants', badge: 'hide', badgeText: 'Hidden', badgeTitle: 'HIDEFIELD from rule: Hide residence for infants' })
: field('Residence', '', { badge: 'hide', badgeText: 'Hidden', badgeTitle: 'HIDEFIELD from rule: Hide residence for infants' }),
field('Mother\u2019s name', '', { badge: 'mandatory', badgeText: 'Required by rule', badgeTitle: 'SETMANDATORYFIELD from rule: Require mother\u2019s name for newborns', ring: '`#ffa902`', help: 'Required while the child is under 2 months old.', helpFg: '`#6f3205`' }),
] },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/playground/design/Program` Rules Playground App.dc.html around lines 467
- 469, Update the residence field branch near the hidden registration row so it
returns a badge-only field when ghosts are disabled instead of null. Mirror the
existing badge-only behavior used by the corresponding stageSections and
eventSections branches, preserving the current ghost rendering when g is enabled
and keeping the hide badge and label visible otherwise.

Comment thread apps/playground/src/components/programs/EnrollmentRail.tsx
Comment thread apps/playground/src/components/programs/forms/ProgramRegistrationForm.tsx Outdated
Comment thread apps/playground/src/components/programs/ProgramContextBar.tsx
Comment thread apps/playground/src/components/rules/HiddenSectionPlaceholder.tsx
Comment thread utils/devtools/src/index.ts
Comment thread utils/devtools/src/RulesPanel.tsx Outdated
Comment thread utils/devtools/src/RulesPanel.tsx Outdated
Comment thread utils/devtools/src/RulesPanel.tsx
Comment thread utils/hooks/src/useRuleEffectTrace.test.tsx
- EnrollmentRail: expose selected row state via aria-current
- ProgramContextBar: don't pass '' as SingleSelectField's selected value
- HiddenSectionPlaceholder: interpolate rule name into one translation unit
  instead of concatenating a translated fragment with a raw value
- RuleFeedbackList: don't render a dangling ": " for text-only feedback,
  which has no value to append
- ProgramRegistrationForm/ProgramEventForm: build one memoized rules-metadata
  object (with programStages) and reuse it for the label lookup, feedback
  list, and RulesPanel — fixes stage names showing as raw UIDs in the
  feedback list, and stops rebuilding rule/field/section maps every render
- ProgramPage: "Reset playground" now remounts the active form via a reset
  key, so it actually clears typed field values and rail/event-draft state
  instead of only resetting org unit + date; distinguish a 404 from other
  query failures instead of always showing "Program not found"
- D2Field: reuse the canonical WidgetProps type instead of a duplicate
  same-shaped D2FieldWidgetProps
- RulesPanel: resolve tab labels at render time instead of module load so
  locale changes take effect; fix an inverted guard that made the
  screen-reader evaluation summary always report zero; correct the empty
  rules message now that the catalog isn't filtered by stage
- useRuleEffectTrace test: hoist the tracker metadata fixture so it keeps a
  stable identity across renders instead of forcing rule-engine re-init
- CLAUDE.md/docs/dev-tools.md: update references from the removed
  ProgramRulesPanel/RuleDevtoolsPanel to the merged RulesPanel

Not applied: a finding on RuleAwareField's label-blanking removing the
field's accessible name for assistive tech — a real issue, but fixing it
correctly needs auditing/extending all D2*Field widgets to accept an
explicit id/aria-labelledby, not a quick patch; flagged as a follow-up.
Also not applied: a fix in the static design-prototype .dc.html file under
apps/playground/design/, which is reference material excluded from lint/
build, not application source.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
cursor[bot]
cursor Bot approved these changes Aug 1, 2026 •
RuleAwareField blanks each widget's fieldConfig.label to avoid rendering
a duplicate visible label, but @dhis2/ui's field components don't accept
an aria-label/aria-labelledby prop — once label is falsy they render no
label element at all, so the underlying input lost its only accessible
name.

Verified empirically (both live DOM and @dhis2-ui/input's source) that
11 of 13 widget kinds forward `name` straight onto the rendered element's
`id`, while SingleSelectField/CheckboxField (boolean, trueOnly, select)
have no id/name-based labelling mechanism in this @dhis2/ui version at
all — a pre-existing upstream gap. A single `htmlFor` fix on our own
label wouldn't cover those.

Fix: keep our own visible label as the sole accessible name, and after
render, point whichever interactive element the widget actually
produced (input/textarea/the select's custom div) at it via
aria-labelledby. This works uniformly across all widget kinds without
touching any of the 13 D2*Field widget files. Radio-group fields (their
own <fieldset>/<legend>) are left alone since that's already correctly
labelled natively.

Confirmed live for a text field, date field, select field, and
checkboxes that the accessible name now resolves to the visible label
text.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
cursor[bot]
cursor Bot approved these changes Aug 1, 2026 •

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/dev-tools.md`:
- Around line 156-174: Update the architecture statement describing the hooks
dependency to identify `@dhis2-form-utils/hooks` as a runtime dependency rather
than type-only, consistent with RulesPanel’s useFormStore() call. Keep the
surrounding dependency and bundling guidance unchanged.
- Around line 156-174: Update the RulesPanel example comment to accurately
describe its graph-state integration: state that it subscribes to
formStore.fieldStore and reads form.getValues() before passing data to
RuleGraphView, and remove the inaccurate references to useFieldState,
useSectionState, and useFormFeedback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0e763e9e-5afb-48f6-a075-7c1eb4a9f474

📥 Commits

Reviewing files that changed from the base of the PR and between b1454da and ea36de6.

📒 Files selected for processing (14)
  • CLAUDE.md
  • apps/playground/i18n/en.pot
  • apps/playground/src/components/programs/EnrollmentRail.tsx
  • apps/playground/src/components/programs/ProgramContextBar.tsx
  • apps/playground/src/components/programs/forms/ProgramEventForm.tsx
  • apps/playground/src/components/programs/forms/ProgramRegistrationForm.tsx
  • apps/playground/src/components/rules/HiddenSectionPlaceholder.tsx
  • apps/playground/src/components/rules/RuleAwareField.tsx
  • apps/playground/src/components/rules/RuleFeedbackList.tsx
  • apps/playground/src/pages/ProgramPage.tsx
  • components/dhis2-ui/src/fields/D2Field.tsx
  • docs/dev-tools.md
  • utils/devtools/src/RulesPanel.tsx
  • utils/hooks/src/useRuleEffectTrace.test.tsx
🚧 Files skipped from review as they are similar to previous changes (12)
  • apps/playground/src/components/programs/ProgramContextBar.tsx
  • apps/playground/src/components/rules/HiddenSectionPlaceholder.tsx
  • apps/playground/src/pages/ProgramPage.tsx
  • apps/playground/i18n/en.pot
  • apps/playground/src/components/rules/RuleFeedbackList.tsx
  • utils/hooks/src/useRuleEffectTrace.test.tsx
  • utils/devtools/src/RulesPanel.tsx
  • apps/playground/src/components/programs/EnrollmentRail.tsx
  • apps/playground/src/components/programs/forms/ProgramRegistrationForm.tsx
  • apps/playground/src/components/programs/forms/ProgramEventForm.tsx
  • components/dhis2-ui/src/fields/D2Field.tsx
  • apps/playground/src/components/rules/RuleAwareField.tsx

Comment thread docs/dev-tools.md Outdated
cursor[bot]
cursor Bot approved these changes Aug 2, 2026 •
- devtools/hooks dependency isn't type-only: RulesPanel calls useFormStore(),
  a runtime hook, not just types
- RulesPanel's example comment named useFieldState/useSectionState/
  useFormFeedback, none of which it actually calls — it subscribes to
  formStore.fieldStore directly and reads form.getValues()

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/playground/src/components/programs/ProgramListTable.tsx (1)

149-166: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a five-column span for table state and pagination rows.

The table defines five columns: Program, Type, Stages, Last updated, and the navigation icon. colSpan="6" at Line 150, Line 166, and Line 179 can create an extra column and misalign the loading, empty-state, and pagination rows. Change these values to colSpan="5".

Proposed fix
-<DataTableCell colSpan="6">
+<DataTableCell colSpan="5">

Also applies to: 177-193

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/playground/src/components/programs/ProgramListTable.tsx` around lines
149 - 166, Update the colSpan values on the loading, empty-state, and pagination
DataTableCell elements in ProgramListTable so they use five columns instead of
six, preserving alignment with the table’s five defined columns.
🧹 Nitpick comments (1)
apps/playground/src/components/programs/ProgramListTable.tsx (1)

57-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add tests for keyboard row activation.

apps/playground/src/components/programs/ProgramListTable.test.tsx currently verifies only a mouse click. Add tests for Enter and Space on the role="button" cell.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/playground/src/components/programs/ProgramListTable.tsx` around lines 57
- 62, Add tests in ProgramListTable.test.tsx covering keyboard activation of the
role="button" cell: dispatch Enter and Space key events, verify each prevents
default behavior, and confirm onProgramSelect is called with the selected
program.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/playground/src/components/programs/ProgramListTable.tsx`:
- Around line 85-88: Update the stage DataTableCell and its corresponding
DataTableColumnHeader in ProgramListTable to use the same alignment, changing
the cell’s align setting to match the header’s existing right alignment.

---

Outside diff comments:
In `@apps/playground/src/components/programs/ProgramListTable.tsx`:
- Around line 149-166: Update the colSpan values on the loading, empty-state,
and pagination DataTableCell elements in ProgramListTable so they use five
columns instead of six, preserving alignment with the table’s five defined
columns.

---

Nitpick comments:
In `@apps/playground/src/components/programs/ProgramListTable.tsx`:
- Around line 57-62: Add tests in ProgramListTable.test.tsx covering keyboard
activation of the role="button" cell: dispatch Enter and Space key events,
verify each prevents default behavior, and confirm onProgramSelect is called
with the selected program.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ff12b82c-f5dc-4da0-8126-246d634b191e

📥 Commits

Reviewing files that changed from the base of the PR and between ea36de6 and 54bdeac.

📒 Files selected for processing (4)
  • .claude/launch.json
  • apps/playground/src/components/programs/ProgramListFilters.tsx
  • apps/playground/src/components/programs/ProgramListTable.tsx
  • apps/playground/src/pages/ProgramListPage.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/playground/src/components/programs/ProgramListFilters.tsx
  • apps/playground/src/pages/ProgramListPage.tsx

Comment on lines +85 to +88
<DataTableCell
onClick={handleSelect}
align="center"
className="w-30 tabular-nums"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep the stages header and values aligned consistently.

DataTableColumnHeader at Line 136 uses align="right", but the stage cell at Line 87 uses align="center". Apply the same alignment to both elements.

Also applies to: 135-140

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/playground/src/components/programs/ProgramListTable.tsx` around lines 85
- 88, Update the stage DataTableCell and its corresponding DataTableColumnHeader
in ProgramListTable to use the same alignment, changing the cell’s align setting
to match the header’s existing right alignment.

@nnkogift
nnkogift merged commit 4a071f1 into main Aug 2, 2026
12 checks passed

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approved: Cursor Security Agent passed with no findings requiring human review; Cursor Bugbot was not present on this PR, so that signal was skipped. No reviewers were assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant