Repository navigation
chore: repo-wide coding-standards cleanup pass - #18
Conversation
Removes the unused utils/hooks fields barrel, consolidates the DefaultFormValue type duplicated across useEventForm/useTrackerForm, extracts repeated magic numbers (devtools trace maxEntries, storybook interaction timeouts) into named constants, and corrects package.json dependency declarations flagged by fallow (missing @dhis2/d2-i18n and @testing-library/react in the playground app, unused dayjs/mockdate in storybook, misdeclared @dhis2/api-types/@dhis2/data-engine in hooks). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
components/mui/src/fields/widgets/Fields.tsx bundled 14 field components in a single file, unlike the dhis2-ui and mantine adapters which already split by domain. Splits it into TextField, NumericFields, ChoiceFields, and UnsupportedField to match that layout, and resolves the CRITICAL cyclomatic-complexity hotspot fallow flagged on the combined file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extracts toStringValue, toRuleAction, and normalizeEffect — identical between context.ts and enrollmentContext.ts (fallow's top-ranked duplication target: 4 clone groups, 128 duplicated lines) — into a shared ruleInterop module. Also replaces two switch-based dispatch functions with declarative lookup tables to resolve fallow's complexity hotspots: buildSchema's valueTypeToZod (28 cyclomatic -> grouped value-type-to-schema map) and evaluate's applyEffect (34 cyclomatic -> a map of small per-action-type appliers). Behavior is unchanged; existing fixture-based unit tests cover both. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… file fallow's dead-code detector doesn't recognize the DHIS2 App Platform's d2.config.js entryPoints convention, so it flagged AppWrapper.tsx (the app's real entry, declared only in d2.config.js) as unused — cascading into 55 files (96% of the whole-repo dead-code count) being misreported as dead across the playground. Adding an explicit entry glob to .fallowrc.json fixes the false-positive cascade (55 -> 13 remaining, all legitimate config/test files not reached via imports). While verifying the corrected dead-file list, found one file the cascade had been masking: components/mantine/src/fields/widgets/DateFields.tsx duplicates D2DateField/D2AgeField, but mantine's widgets/index.ts already imports those from TextField.tsx — nothing references this file. Removed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 43 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe PR adds Fallow skills, review hooks, references, configuration, and CI badge publication. It reorganizes MUI field widgets, centralizes form and rule conversions, updates metadata schemas, and applies supporting dependency, timeout, and devtools constant changes. ChangesFallow review and analysis tooling
Form and rule utility changes
Supporting package and test maintenance
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant ClaudeCode
participant SessionInitHook
participant FeedFile
participant FeedbackHook
ClaudeCode->>SessionInitHook: start or resume session
SessionInitHook->>FeedFile: create or register feed.jsonl
ClaudeCode->>FeedbackHook: report feed.jsonl change
FeedbackHook->>FeedFile: read new JSONL notes
FeedbackHook-->>ClaudeCode: emit reviewer feedback context
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
| run: fallow health --format badge > badge.svg | ||
|
|
||
| - name: Publish badge | ||
| uses: peaceiris/actions-gh-pages@v4 |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: MEDIUM
Using peaceiris/actions-gh-pages@v4 pins to a mutable tag rather than an immutable commit SHA. If that upstream tag is retagged or compromised, the workflow could run attacker-controlled code in a job that has write-capable repository credentials.
Impact: A compromised action release could lead to unauthorized changes in repository-published artifacts/branches during CI.
Reviewed by Cursor Security Reviewer for commit 98be014. Configure here.
There was a problem hiding this comment.
Stale comment
Left a non-blocking comment (not approved): Cursor Security Agent completed with 1 unresolved medium-severity finding that needs human attention. Cursor Bugbot was not present; no reviewers were assigned because no eligible non-author reviewers are available.
Sent by Cursor Approval Agent: Pull Request Router and Approver
…ntain permissions' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…p' into refactor/coding-standards-cleanup
There was a problem hiding this comment.
Actionable comments posted: 13
🧹 Nitpick comments (1)
components/mui/src/fields/widgets/TextField.tsx (1)
5-10: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winKeep the public widget props as
WidgetProps.Line 10 exposes
typeandmultilineas widget props. KeepD2TextFieldaccepting onlyWidgetProps. Move these options into a private renderer that the specialized widgets call.As per coding guidelines, “Widget props must use
WidgetProps = { control: FieldControlReturn }.”🤖 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 `@components/mui/src/fields/widgets/TextField.tsx` around lines 5 - 10, Update D2TextField to accept only WidgetProps and remove type/multiline from its public signature. Move these options into a private renderer used by the specialized widgets, ensuring all public widget components continue to use the WidgetProps contract.Source: Coding guidelines
🤖 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 @.agents/skills/fallow-review/hooks/settings.snippet.json:
- Line 9: Update both hook commands in
.agents/skills/fallow-review/hooks/settings.snippet.json: lines 9-9 should
target .claude/skills/fallow-review/hooks/fallow-review-session-init.sh, and
lines 20-20 should target
.claude/skills/fallow-review/hooks/fallow-review-on-feedback.sh, preserving the
CLAUDE_PROJECT_DIR prefix.
In @.agents/skills/fallow-review/references/tradeoff-elicitation.md:
- Around line 18-25: The trade-off output schema must expose the
fallow-validated change-anchor fields instead of treating them as file-line
anchors. In .agents/skills/fallow-review/references/tradeoff-elicitation.md
lines 18-25, update the JSON shape and labels to use the actual change_anchor
field and distinguish change-region anchors from signal anchors; in
.agents/skills/fallow-review/SKILL.md lines 81-83, align the documented judgment
fields with that same contract.
In @.agents/skills/fallow-review/SKILL.md:
- Line 172: The “Act” step in SKILL.md must document how accepted judgments
become feed JSONL records. Define the exact mapping from each judgment’s
signal_id or change_anchor, framing, and concern into the hook-consumed
target.kind, target.value, and note fields, or reference a helper that performs
this conversion before appending to .fallow-review/feed.jsonl.
In @.agents/skills/fallow/references/cli-reference.md:
- Around line 540-566: Reconcile the Fallow version shown in the health JSON
example with the repository’s pinned `fallow@2.77.0` dependency. Update the
documented `"version"` value and any related version-specific examples or fields
in the reference so they match supported behavior, or explicitly document the
intentional delta if the example must remain on 3.11.0.
In @.agents/skills/fallow/references/gotchas.md:
- Around line 639-641: Update the fenced code block containing the fallow
license refresh message to specify the text language identifier, changing the
opening fence to use text while preserving the message content.
In @.fallowrc.json:
- Around line 17-26: Remove all // comments from .fallowrc.json, including the
explanatory comments around minOccurrences and the commented ignore
configuration, so the file contains valid JSON while preserving the active
minOccurrences setting.
In @.github/workflows/ci.yml:
- Around line 116-117: Update the “Generate health badge” job to check out the
repository, set up pnpm and Node with the workflow’s existing versions, and
install dependencies using pnpm install --frozen-lockfile before generating the
badge. Invoke the project-local CLI through pnpm exec fallow health --format
badge so it uses the repository configuration.
- Around line 117-126: Update the badge generation and Publish badge step in the
workflow to write badge.svg under the dedicated badge-output directory, set
peaceiris/actions-gh-pages@v4's publish_dir to badge-output, and remove the
unsupported include_files input.
In `@components/mui/src/fields/widgets/ChoiceFields.tsx`:
- Around line 12-31: D2BooleanField must resolve and display field validation
feedback. Update D2BooleanField to call resolveFieldValidation, pass the
resulting error state to an appropriate error-aware form control wrapping
ToggleButtonGroup, and render the validation message while preserving the
existing boolean toggle behavior.
- Around line 38-53: Update the checkbox rendering around FormControlLabel so it
is wrapped in FormControl, remove the unsupported helperText prop from
FormControlLabel, and render validationText conditionally with FormHelperText
when hasError is true. Preserve the existing checkbox behavior, label, required
state, and validation text.
In `@components/mui/src/fields/widgets/NumericFields.tsx`:
- Around line 6-12: Update D2NumberField to pass step="any" through
slotProps.htmlInput on D2TextField, allowing fractional values while retaining
type="number". Leave D2IntegerField unchanged so it continues enforcing integer
input.
In `@components/mui/src/fields/widgets/TextField.tsx`:
- Around line 56-65: Update computeAgeFromDob to parse the validated date
components as a local calendar date using the year, month minus one, and day
constructor arguments, replacing new Date(dob). Preserve the existing age
calculation and invalid/future-date handling.
In `@README.md`:
- Around line 7-8: Update the fallow-badge job in ci.yml so it independently
checks out the repository, sets up the required runtime and pnpm, and installs
dependencies before generating the badge. Replace the bare fallow health
invocation in Generate health badge with pnpm exec fallow health --format badge
redirected to badge.svg, then preserve the existing publishing flow.
---
Nitpick comments:
In `@components/mui/src/fields/widgets/TextField.tsx`:
- Around line 5-10: Update D2TextField to accept only WidgetProps and remove
type/multiline from its public signature. Move these options into a private
renderer used by the specialized widgets, ensuring all public widget components
continue to use the WidgetProps contract.
🪄 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: 93b6ef07-716b-48b5-9bde-7ccdbcc260fc
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (48)
.agents/skills/fallow-review/SKILL.md.agents/skills/fallow-review/agents/openai.yaml.agents/skills/fallow-review/hooks/fallow-review-on-feedback.sh.agents/skills/fallow-review/hooks/fallow-review-session-init.sh.agents/skills/fallow-review/hooks/settings.snippet.json.agents/skills/fallow-review/references/tradeoff-elicitation.md.agents/skills/fallow/SKILL.md.agents/skills/fallow/agents/openai.yaml.agents/skills/fallow/references/cli-reference.md.agents/skills/fallow/references/gotchas.md.agents/skills/fallow/references/mcp.md.agents/skills/fallow/references/node-bindings.md.agents/skills/fallow/references/patterns.md.claude/skills/fallow.claude/skills/fallow-review.fallowrc.json.github/workflows/ci.yml.gitignore.idea/vcs.xml.prettierignoreREADME.mdapps/playground/package.jsonapps/storybook/interactions/ancInteractions.tsapps/storybook/interactions/childProgrammeInteractions.tsapps/storybook/interactions/waitTimeouts.tsapps/storybook/package.jsoncomponents/mantine/src/fields/widgets/DateFields.tsxcomponents/mui/src/fields/widgets/ChoiceFields.tsxcomponents/mui/src/fields/widgets/Fields.tsxcomponents/mui/src/fields/widgets/NumericFields.tsxcomponents/mui/src/fields/widgets/TextField.tsxcomponents/mui/src/fields/widgets/UnsupportedField.tsxcomponents/mui/src/fields/widgets/index.tspackages/metadata/src/buildSchema.tsskills-lock.jsonutils/devtools/src/attach.tsutils/devtools/src/constants.tsutils/devtools/src/traceStore.tsutils/hooks/package.jsonutils/hooks/src/fields/index.tsutils/hooks/src/formValue.tsutils/hooks/src/index.tsutils/hooks/src/useEventForm.tsutils/hooks/src/useTrackerForm.tsutils/rules/src/context.tsutils/rules/src/enrollmentContext.tsutils/rules/src/evaluate.tsutils/rules/src/ruleInterop.ts
💤 Files with no reviewable changes (6)
- components/mantine/src/fields/widgets/DateFields.tsx
- components/mui/src/fields/widgets/Fields.tsx
- utils/hooks/src/fields/index.ts
- apps/storybook/package.json
- utils/hooks/package.json
- .idea/vcs.xml
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/fallow-review-session-init.sh" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Point both hook commands at the installed skill hooks.
This change creates .claude/skills/fallow-review, but it does not create .claude/hooks/. Both commands will fail when Claude invokes the hooks.
.agents/skills/fallow-review/hooks/settings.snippet.json#L9-L9: change the command to${CLAUDE_PROJECT_DIR}/.claude/skills/fallow-review/hooks/fallow-review-session-init.sh..agents/skills/fallow-review/hooks/settings.snippet.json#L20-L20: change the command to${CLAUDE_PROJECT_DIR}/.claude/skills/fallow-review/hooks/fallow-review-on-feedback.sh.
📍 Affects 1 file
.agents/skills/fallow-review/hooks/settings.snippet.json#L9-L9(this comment).agents/skills/fallow-review/hooks/settings.snippet.json#L20-L20
🤖 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 @.agents/skills/fallow-review/hooks/settings.snippet.json at line 9, Update
both hook commands in .agents/skills/fallow-review/hooks/settings.snippet.json:
lines 9-9 should target
.claude/skills/fallow-review/hooks/fallow-review-session-init.sh, and lines
20-20 should target
.claude/skills/fallow-review/hooks/fallow-review-on-feedback.sh, preserving the
CLAUDE_PROJECT_DIR prefix.
| by fallow. What fallow now DOES validate is the ANCHOR: a trade-off may cite a | ||
| `change_anchor` (a `chg:` id from the guide's `change_anchors`) and fallow | ||
| post-validates it on reentry the same way it validates a `signal_id`, rejecting an | ||
| anchor it never emitted (`unknown-change-anchor`). The accepted judgment carries | ||
| `anchor_kind: "change"` to mark it as the WEAKER, region-level anchor: it proves | ||
| only that the region changed, not that a graph finding exists there (that is | ||
| `anchor_kind: "signal"`). So the anchor is now fallow-grade; the framing prose | ||
| remains an agent-layer aid whose discipline is the prompt's. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== relevant docs =="
for f in ".agents/skills/fallow-review/SKILL.md" ".agents/skills/fallow-review/references/tradeoff-elicitation.md"; do
if [ -f "$f" ]; then
echo "-- $f --"
sed -n '70,90p;1,40p' "$f"
else
echo "MISSING $f"
fi
done
echo
echo "== search walkthrough/judgment/tradeoff symbols =="
rg -n "tradeoff|change_anchor|signal_id|walkthrough|fallow-review|unknown-change-anchor|Determination|deterministic" .agents/skills/fallow-review . 2>/dev/null | head -n 200Repository: nnkogift/dhis2-form-utils
Length of output: 23483
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== tradeoff prompt relevant sections =="
sed -n '120,170p;140,155p' .agents/skills/fallow-review/references/tradeoff-elicitation.md | cat -n
echo
echo "== exact references to tradeoffs.json and post-validate text in fallow-review docs =="
python3 - <<'PY'
from pathlib import Path
for path, start, end in [
(".agents/skills/fallow-review/SKILL.md", 67, 128),
(".agents/skills/fallow-review/references/tradeoff-elicitation.md", 1, 25),
]:
p = Path(path)
lines = p.read_text().splitlines()
print(f"-- {path}:{start}-{end} --")
for i in range(start, min(end+1, len(lines))+1):
if "tradeoffs" in lines[i-1] or "walkthrough" in lines[i-1] or "change_anchor" in lines[i-1] or "signal_id" in lines[i-1] or "post-validates" in lines[i-1]:
print(f"{i:04}: {lines[i-1]}")
PY
echo
echo "== programmatic check for stated invariant: file mentions tradeoffs.json excluded from --walkthrough-file; --walkthrough-file doc only lists signal_id? =="
python3 - <<'PY'
from pathlib import Path
skill = Path(".agents/skills/fallow-review/SKILL.md").read_text()
tradeoff = Path(".agents/skills/fallow-review/references/tradeoff-elicitation.md").read_text()
checks = {
"tradeoff_18_25_mentions_change_anchor_as_post_validated": 'change_anchor' in "\n".join(tradeoff.splitlines()[17:25]) and 'post-validates' in "\n".join(tradeoff.splitlines()[17:25]),
"tradeoff_18_25_mentions_signal_id_validation": 'signal_id' in "\n".join(tradeoff.splitlines()[17:25]) and 'validates' in "\n".join(tradeoff.splitlines()[17:25]),
"skill_81_83_mentions_tradeoffs_json": 'tradeoffs.json' in "\n".join(skill.splitlines()[80:84]),
"skill_81_83_says_step_not_part_of_post_validate": 'NOT part of the post-validate agent-contract loop' in "\n".join(skill.splitlines()[80:84]),
"skill_81_83_says_tradeoffs_never_round_tripped": 'never round-tripped' in "\n".join(skill.splitlines()[80:84]),
"skill_81_83_claims_walkthrough_file_validates_only_signal_ids": 'walkthrough-file' in "\n".join(skill.splitlines()[80:84]) and 'only validates emitted `signal_id`s' in "\n".join(skill.splitlines()[80:84]),
"skill_92_128_describes_change_anchor_reentry_validation": 'change_anchor' in skill[skill.index('The ANCHOR is now fallow-validated'):skill.index('When a review surface')],
"skill_92_128_describes_walkthrough_file_reentry_validation_labels": 'unanchored-signal-id' in skill[skill.index('The agent-contract loop above'):skill.index('## Human-in-the-loop walkthrough')],
}
for k, v in checks.items():
print(f"{k}: {v}")
PYRepository: nnkogift/dhis2-form-utils
Length of output: 7778
Make the trade-off output shape use the actual change_anchor fields.
The trade-off prompt schema only uses anchor as a file:line or cross-cutting, and SKILL.md#L67 already calls out that judgments may use change_anchor. If these items are meant to support fallow-validated change-region anchoring, add the matching JSON field(s) and labels here instead of mixing change_anchor prose into a file-line anchor contract.
📍 Affects 2 files
.agents/skills/fallow-review/references/tradeoff-elicitation.md#L18-L25(this comment).agents/skills/fallow-review/SKILL.md#L81-L83
🤖 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 @.agents/skills/fallow-review/references/tradeoff-elicitation.md around lines
18 - 25, The trade-off output schema must expose the fallow-validated
change-anchor fields instead of treating them as file-line anchors. In
.agents/skills/fallow-review/references/tradeoff-elicitation.md lines 18-25,
update the JSON shape and labels to use the actual change_anchor field and
distinguish change-region anchors from signal anchors; in
.agents/skills/fallow-review/SKILL.md lines 81-83, align the documented judgment
fields with that same contract.
| - `rejected` `unanchored-signal-id` / `unknown-change-anchor`: the human cited something fallow never emitted. Re-anchor to a real signal or region; do not invent one. | ||
| - `stale: true` (`stale-snapshot`): the tree moved since `guide.json` was fetched. Re-fetch the guide, re-capture, resubmit. | ||
|
|
||
| 4. **Act:** relay the accepted human verdicts into the coding session in place, or append them to `.fallow-review/feed.jsonl` so the live-injection hooks (below) carry them to the session that wrote the code. Either way the note arrives anchored and fenced, never as a fallow-grade fact. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Document the feed JSONL conversion.
The hook in .agents/skills/fallow-review/hooks/fallow-review-on-feedback.sh reads target.kind, target.value, and note from each JSONL record. The walkthrough example produces judgments[] with signal_id or change_anchor, framing, and concern. Line 172 does not define the conversion. Appending the walkthrough envelope directly would produce invalid JSONL and empty injected notes.
Add the exact record format or provide a helper that maps each accepted judgment to one feed record.
🤖 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 @.agents/skills/fallow-review/SKILL.md at line 172, The “Act” step in
SKILL.md must document how accepted judgments become feed JSONL records. Define
the exact mapping from each judgment’s signal_id or change_anchor, framing, and
concern into the hook-consumed target.kind, target.value, and note fields, or
reference a helper that performs this conversion before appending to
.fallow-review/feed.jsonl.
| ```json | ||
| { | ||
| "kind": "health", | ||
| "schema_version": 7, | ||
| "version": "3.11.0", | ||
| "elapsed_ms": 32, | ||
| "summary": { | ||
| "files_analyzed": 482, | ||
| "functions_analyzed": 3200, | ||
| "functions_above_threshold": 3, | ||
| "max_cyclomatic_threshold": 20, | ||
| "max_cognitive_threshold": 15 | ||
| }, | ||
| "findings": [ | ||
| { | ||
| "path": "src/parser.ts", | ||
| "name": "parseExpression", | ||
| "line": 42, | ||
| "col": 0, | ||
| "cyclomatic": 28, | ||
| "cognitive": 22, | ||
| "line_count": 95, | ||
| "exceeded": "both" | ||
| } | ||
| ] | ||
| } | ||
| ``` |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Compare the pinned fallow version against the version literal used in the skill docs.
rg -n '"specifier".*fallow|^\s*fallow@' pnpm-lock.yaml | head -20
rg -n '"version": "3\.11\.0"' .agents/skills/fallow/references/cli-reference.md | head -5Repository: nnkogift/dhis2-form-utils
Length of output: 354
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== workspace/package references =="
grep -RIno '"fallow"|fallow@|fallow:' package.json pnpm-lock.yaml . || true
echo
echo "== fallow references in agent skill files (first matches per file) =="
fd . .agents/skills/fallow -t f 2>/dev/null | while read -r f; do
if rg -n 'fallow|3\.11\.0|2\.77\.0|2\.76\.0' "$f" >/tmp/findings_match.$$ 2>/dev/null; then
echo "--- $f"
sed -n '1,40p' "$f"
fi
done || true
rm -f /tmp/findings_match.$$
echo
echo "== cli-reference.md examples around version and schema/context =="
sed -n '510,580p' .agents/skills/fallow/references/cli-reference.md
echo
sed -n '920,970p' .agents/skills/fallow/references/cli-reference.md
echo
sed -n '1100,1170p' .agents/skills/fallow/references/cli-reference.md
echo
sed -n '1900,1955p' .agents/skills/fallow/references/cli-reference.mdRepository: nnkogift/dhis2-form-utils
Length of output: 50383
Reconcile the documented Fallow version with the installed dependency.
This repo pins and resolves fallow@2.77.0, but this reference repeatedly shows JSON examples with "version": "3.11.0". Update the example versions, pin an appropriate release, or document the intentional version delta so agents do not request unsupported command flags or JSON fields from 2.77.0.
🤖 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 @.agents/skills/fallow/references/cli-reference.md around lines 540 - 566,
Reconcile the Fallow version shown in the health JSON example with the
repository’s pinned `fallow@2.77.0` dependency. Update the documented
`"version"` value and any related version-specific examples or fields in the
reference so they match supported behavior, or explicitly document the
intentional delta if the example must remain on 3.11.0.
| ``` | ||
| fallow license refresh: your stored license is too stale to refresh. Reactivate with: fallow license activate --trial --email <addr> (HTTP 401, code token_stale) | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a language identifier to the fenced code block.
The fenced block at line 639 has no language tag, unlike the surrounding bash and jsonc blocks in this file. Add text so the block renders consistently.
📝 Proposed fix
-```
+```text
fallow license refresh: your stored license is too stale to refresh. Reactivate with: fallow license activate --trial --email <addr> (HTTP 401, code token_stale)</details>
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **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.
```suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.23.1)
[warning] 639-639: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 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 @.agents/skills/fallow/references/gotchas.md around lines 639 - 641, Update
the fenced code block containing the fallow license refresh message to specify
the text language identifier, changing the opening fence to use text while
preserving the message content.
Source: Linters/SAST tools
| export function D2BooleanField({ control }: WidgetProps) { | ||
| const { fieldConfig, field, isDisabled } = control; | ||
|
|
||
| return ( | ||
| <ToggleButtonGroup | ||
| exclusive | ||
| value={field.value as string} | ||
| disabled={isDisabled} | ||
| onChange={(_event, value: string | null) => { | ||
| field.onChange(value ?? ''); | ||
| }} | ||
| onBlur={field.onBlur} | ||
| aria-label={fieldConfig.label} | ||
| > | ||
| <ToggleButton value="true">Yes</ToggleButton> | ||
| <ToggleButton value="false">No</ToggleButton> | ||
| <ToggleButton value="">—</ToggleButton> | ||
| </ToggleButtonGroup> | ||
| ); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Render validation feedback for D2BooleanField.
D2BooleanField does not call resolveFieldValidation. An invalid Boolean field has no error state or validation message. Resolve validation and render the message with an error-aware form control.
As per coding guidelines, “widgets must use resolveFieldValidation.”
🧰 Tools
🪛 ESLint
[error] 13-13: Unsafe assignment of an error typed value.
(@typescript-eslint/no-unsafe-assignment)
[error] 18-18: Unsafe member access .value on a type that cannot be resolved.
(@typescript-eslint/no-unsafe-member-access)
[error] 19-19: Unsafe assignment of an error typed value.
(@typescript-eslint/no-unsafe-assignment)
[error] 21-21: Unsafe call of a type that could not be resolved.
(@typescript-eslint/no-unsafe-call)
[error] 21-21: Unsafe member access .onChange on a type that cannot be resolved.
(@typescript-eslint/no-unsafe-member-access)
[error] 23-23: Unsafe assignment of an error typed value.
(@typescript-eslint/no-unsafe-assignment)
[error] 23-23: Unsafe member access .onBlur on a type that cannot be resolved.
(@typescript-eslint/no-unsafe-member-access)
[error] 24-24: Unsafe assignment of an error typed value.
(@typescript-eslint/no-unsafe-assignment)
[error] 24-24: Unsafe member access .label on a type that cannot be resolved.
(@typescript-eslint/no-unsafe-member-access)
🤖 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 `@components/mui/src/fields/widgets/ChoiceFields.tsx` around lines 12 - 31,
D2BooleanField must resolve and display field validation feedback. Update
D2BooleanField to call resolveFieldValidation, pass the resulting error state to
an appropriate error-aware form control wrapping ToggleButtonGroup, and render
the validation message while preserving the existing boolean toggle behavior.
Source: Coding guidelines
| <FormControlLabel | ||
| control={ | ||
| <Checkbox | ||
| name={field.name} | ||
| checked={field.value === 'true'} | ||
| disabled={isDisabled} | ||
| onChange={(event) => { | ||
| field.onChange(event.target.checked ? 'true' : ''); | ||
| }} | ||
| onBlur={field.onBlur} | ||
| /> | ||
| } | ||
| label={fieldConfig.label} | ||
| required={isMandatory} | ||
| {...(hasError ? { helperText: validationText } : {})} | ||
| /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fd -t f '^package.json$' components -x rg -n -C 2 '"`@mui/material`"|"typecheck"' {}
rg -n -C 4 'FormControlLabel|helperText' components/mui/src/fields/widgets/ChoiceFields.tsxRepository: nnkogift/dhis2-form-utils
Length of output: 2050
🏁 Script executed:
#!/bin/bash
set -euo pipefail
node - <<'JS'
const fs = require('fs');
const path = require('path');
const file = 'components/mui/src/fields/widgets/ChoiceFields.tsx';
const content = fs.readFileSync(file, 'utf8');
const idx = content.indexOf('<FormControlLabel');
const slice = content.slice(Math.max(0, idx - 120), Math.min(content.length, idx + 360));
console.log(slice);
const package = JSON.parse(fs.readFileSync('components/mui/package.json', 'utf8'));
console.log('mui dep:', package.dependencies?.['`@mui/material`'] || package.devDependencies?.['`@mui/material`']);
JS
npm view `@mui/material`@latest peerDependencies `@mui/types` version --json 2>/dev/null | jq -r '{version, peerDependencies}' || true
npm view `@mui/types`@latest version --json 2>/dev/null | jq -r 'if type=="array" then .[-1] else . end' || true
# Inspect installed MUI type declarations if the cache makes them available without repo runtime.
node - <<'JS'
console.log(JSON.stringify(process.env, null, 0).length);
JSRepository: nnkogift/dhis2-form-utils
Length of output: 983
🌐 Web query:
@mui/material 9.0.1 FormControlLabel props helperText TypeScript
💡 Result:
In @mui/material 9.0.1, the FormControlLabel component does not have a helperText prop [1][2]. The FormControlLabel API is strictly defined to handle the label and control (such as Radio, Switch, or Checkbox) [1]. If you need to display helper text alongside a form control that uses FormControlLabel, the standard approach in Material UI is to wrap the component in a FormControl and use the FormHelperText component as a sibling [3][4]. Example pattern: <FormControlLabel control={} label="Accept terms" /> Please check this box to continue. Since FormControlLabel does not natively support helperText, there are no corresponding TypeScript types for such a prop on this component [1][2]. Any attempt to pass helperText to FormControlLabel will result in a TypeScript error (or be treated as an invalid/unknown prop).
Citations:
- 1: https://mui.com/material-ui/api/form-control-label/
- 2: https://next.mui.com/material-ui/api/form-control-label/
- 3: https://mui.com/material-ui/api/form-control/
- 4: https://mui.com/material-ui/api/form-helper-text/
Move validation text to FormHelperText.
FormControlLabel does not accept helperText, so this JSX prop fails TypeScript strict mode and does not render the validation message. Wrap the checkbox input in FormControl and render validationText with FormHelperText.
Proposed fix
-import { Checkbox, FormControlLabel, MenuItem, TextField, ToggleButton, ToggleButtonGroup } from '`@mui/material`';
+import {
+ Checkbox,
+ FormControl,
+ FormControlLabel,
+ FormHelperText,
+ MenuItem,
+ TextField,
+ ToggleButton,
+ ToggleButtonGroup,
+} from '`@mui/material`';
- <FormControlLabel
- control={
+ <FormControl error={hasError}>
+ <FormControlLabel
+ control={
<Checkbox
name={field.name}
checked={field.value === 'true'}
disabled={isDisabled}
onChange={(event) => {
field.onChange(event.target.checked ? 'true' : '');
}}
onBlur={field.onBlur}
/>
- }
- label={fieldConfig.label}
- required={isMandatory}
- {...(hasError ? { helperText: validationText } : {})}
- />
+ }
+ label={fieldConfig.label}
+ required={isMandatory}
+ />
+ {hasError && <FormHelperText>{validationText}</FormHelperText>}
+ </FormControl>📝 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.
| <FormControlLabel | |
| control={ | |
| <Checkbox | |
| name={field.name} | |
| checked={field.value === 'true'} | |
| disabled={isDisabled} | |
| onChange={(event) => { | |
| field.onChange(event.target.checked ? 'true' : ''); | |
| }} | |
| onBlur={field.onBlur} | |
| /> | |
| } | |
| label={fieldConfig.label} | |
| required={isMandatory} | |
| {...(hasError ? { helperText: validationText } : {})} | |
| /> | |
| <FormControl error={hasError}> | |
| <FormControlLabel | |
| control={ | |
| <Checkbox | |
| name={field.name} | |
| checked={field.value === 'true'} | |
| disabled={isDisabled} | |
| onChange={(event) => { | |
| field.onChange(event.target.checked ? 'true' : ''); | |
| }} | |
| onBlur={field.onBlur} | |
| /> | |
| } | |
| label={fieldConfig.label} | |
| required={isMandatory} | |
| /> | |
| {hasError && <FormHelperText>{validationText}</FormHelperText>} | |
| </FormControl> |
🧰 Tools
🪛 ESLint
[error] 41-41: Unsafe assignment of an error typed value.
(@typescript-eslint/no-unsafe-assignment)
[error] 41-41: Unsafe member access .name on a type that cannot be resolved.
(@typescript-eslint/no-unsafe-member-access)
[error] 42-42: Unsafe member access .value on a type that cannot be resolved.
(@typescript-eslint/no-unsafe-member-access)
[error] 43-43: Unsafe assignment of an error typed value.
(@typescript-eslint/no-unsafe-assignment)
[error] 45-45: Unsafe call of a type that could not be resolved.
(@typescript-eslint/no-unsafe-call)
[error] 45-45: Unsafe member access .onChange on a type that cannot be resolved.
(@typescript-eslint/no-unsafe-member-access)
[error] 47-47: Unsafe assignment of an error typed value.
(@typescript-eslint/no-unsafe-assignment)
[error] 47-47: Unsafe member access .onBlur on a type that cannot be resolved.
(@typescript-eslint/no-unsafe-member-access)
[error] 50-50: Unsafe assignment of an error typed value.
(@typescript-eslint/no-unsafe-assignment)
[error] 50-50: Unsafe member access .label on a type that cannot be resolved.
(@typescript-eslint/no-unsafe-member-access)
[error] 51-51: Unsafe assignment of an error typed value.
(@typescript-eslint/no-unsafe-assignment)
[error] 52-52: Unsafe assignment of an error typed value.
(@typescript-eslint/no-unsafe-assignment)
🤖 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 `@components/mui/src/fields/widgets/ChoiceFields.tsx` around lines 38 - 53,
Update the checkbox rendering around FormControlLabel so it is wrapped in
FormControl, remove the unsupported helperText prop from FormControlLabel, and
render validationText conditionally with FormHelperText when hasError is true.
Preserve the existing checkbox behavior, label, required state, and validation
text.
| export function D2NumberField(props: WidgetProps) { | ||
| return <D2TextField {...props} type="number" />; | ||
| } | ||
|
|
||
| export function D2IntegerField(props: WidgetProps) { | ||
| return <D2TextField {...props} type="number" />; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate metadata and tests that distinguish decimal NUMBER fields from INTEGER fields.
rg -n -C 3 \
--glob '*.{ts,tsx}' \
'D2NumberField|D2IntegerField|widgetKind.*number|widgetKind.*integer|valueType.*NUMBER|valueType.*INTEGER' \
components packages utilsRepository: nnkogift/dhis2-form-utils
Length of output: 23520
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the numeric widgets and their parent text field props/type handling.
printf '\n--- components/mui/src/fields/widgets/NumericFields.tsx ---\n'
cat -n components/mui/src/fields/widgets/NumericFields.tsx
printf '\n--- components/mui/src/fields/widgets/TextField.tsx ---\n'
cat -n components/mui/src/fields/widgets/TextField.tsx
printf '\n--- Numeric field mappings/usages ---\n'
rg -n -C 4 \
--glob '*.{ts,tsx}' \
'(widgetKind|widgetKindValue|D2NumberField|D2IntegerField|INTEGER|NUMBER|Integer|Number)' \
components packages utils | head -n 240
printf '\n--- package metadata for value types ---\n'
cat -n packages/metadata/src/buildSchema.ts | sed -n '1,120p'Repository: nnkogift/dhis2-form-utils
Length of output: 26477
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect D2TextField type forwarding and locate any step-prop support for other number inputs.
printf '\n--- locate step props in components ---\n'
rg -n --glob '*.{ts,tsx}' -C 3 'step=|slotProps.*htmlInput|htmlInput|D2TextField|IntegerInput|NumericInput' components packages
printf '\n--- browser/JS numeric input step semantics probe ---\n'
node - <<'JS'
const inputs = [
{ attrs: { type: 'number' } },
{ attrs: { type: 'number', step: 'any' } },
{ attrs: { type: 'number', step: '1' } },
];
let skipped = false;
for (const input of inputs) {
try {
if (typeof document === 'undefined') {
console.log(`jsdom unavailable, skipped input ${JSON.stringify(input.attrs)}`);
skipped = true;
continue;
}
const el = Document.prototype.createElement.call({}, 'input', input.attrs);
el.value = '1.5';
const result = el.checkValidity();
const message = result ? 'valid' : 'invalid';
console.log(`#${JSON.stringify(input.attrs)} checkValidity=${message} error=${el.validationMessage || ''}`);
} catch (error) {
if (!skipped) console.log(`probe failed: ${error.name}: ${error.message}`);
}
}
JSRepository: nnkogift/dhis2-form-utils
Length of output: 13728
🌐 Web query:
MDN HTML input[global] step attribute default value step="1" number field
💡 Result:
For an HTML element, the default value of the step attribute is 1 [1][2][3]. This default step of 1 means that, by default, the input only accepts integers (whole numbers) [1][3]. If a user attempts to enter a non-integer value, such as 4.2, it will be considered invalid and will match the:invalid CSS pseudo-class [3]. There are two primary ways to allow non-integer (floating-point) values: 1. Setting step="any": This removes the step constraint entirely, allowing any floating-point number [1][3]. 2. Setting a smaller step value: You can specify a decimal value for the step attribute (e.g., step="0.1" or step="0.01") to allow input with that specific precision [1][2][3]. Additionally, if the "step base" (determined by the min attribute, or the value attribute if min is not provided) is a non-integer, those non-integer values may remain valid despite the default step of 1 [1][4].
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/number
- 2: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/step
- 3: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input
- 4: https://html.spec.whatwg.org/dev/input.html
Allow fractional values in D2NumberField.
D2NumberField and D2IntegerField both render the same type="number" input, and a number input defaults to step="1" so fractional entries fail native validation. Pass step="any" to the HTML input for D2NumberField; MUI forwards this through slotProps.htmlInput.
🤖 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 `@components/mui/src/fields/widgets/NumericFields.tsx` around lines 6 - 12,
Update D2NumberField to pass step="any" through slotProps.htmlInput on
D2TextField, allowing fractional values while retaining type="number". Leave
D2IntegerField unchanged so it continues enforcing integer input.
| function computeAgeFromDob(dob: string): string { | ||
| if (!/^\d{4}-\d{2}-\d{2}$/.test(dob)) return ''; | ||
| const birth = new Date(dob); | ||
| const today = new Date(); | ||
| let age = today.getFullYear() - birth.getFullYear(); | ||
| const monthDiff = today.getMonth() - birth.getMonth(); | ||
| if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) { | ||
| age -= 1; | ||
| } | ||
| return age >= 0 ? String(age) : ''; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
TZ=America/Los_Angeles node - <<'NODE'
const dob = '2000-08-04';
const birth = new Date(dob);
const today = new Date(2026, 7, 3);
let age = today.getFullYear() - birth.getFullYear();
if (
today.getMonth() < birth.getMonth() ||
(today.getMonth() === birth.getMonth() && today.getDate() < birth.getDate())
) {
age -= 1;
}
console.log({ parsedLocalDate: birth.toString(), computedAge: age, expectedAge: 25 });
NODERepository: nnkogift/dhis2-form-utils
Length of output: 284
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Locate TextField.tsx:"
git ls-files | grep -F 'components/mui/src/fields/widgets/TextField.tsx' || true
echo
echo "Relevant file excerpt:"
if [ -f components/mui/src/fields/widgets/TextField.tsx ]; then
nl -ba components/mui/src/fields/widgets/TextField.tsx | sed -n '1,140p'
fi
echo
echo "Behavioral probe for calendar-date fix:"
TZ=America/Los_Angeles node - <<'NODE'
function computeAgeFromDob(dob) {
if (!/^\d{4}-\d{2}-\d{2}$/.test(dob)) return '';
const birth = new Date(dob);
const today = new Date(2026, 7, 3);
let age = today.getFullYear() - birth.getFullYear();
if (today.getMonth() < birth.getMonth() || (today.getMonth() === birth.getMonth() && today.getDate() < birth.getDate())) age -= 1;
return age >= 0 ? String(age) : '';
}
function computeAgeFromDobLocal(dob) {
if (!/^\d{4}-\d{2}-\d{2}$/.test(dob)) return '';
const year = parseInt(dob.slice(0, 4), 10);
const month = parseInt(dob.slice(5, 7), 10);
const day = parseInt(dob.slice(8, 10), 10);
const birth = new Date(year, month - 1, day);
const today = new Date(2026, 7, 3);
let age = today.getFullYear() - birth.getFullYear();
if (today.getMonth() < birth.getMonth() || (today.getMonth() === birth.getMonth() && today.getDate() < birth.getDate())) age -= 1;
return age >= 0 ? String(age) : '';
}
for (const [dob, expected] of [
['2000-08-04', 25],
['2000-08-05', 25],
['2000-08-03', 26]
]) {
console.log({ dob, ageCurrent: computeAgeFromDob(dob), ageLocalOnly: computeAgeFromDopLocal(dob), expectedAge: expected });
}
NODERepository: nnkogift/dhis2-form-utils
Length of output: 298
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Relevant file excerpt:"
head -n 140 components/mui/src/fields/widgets/TextField.tsx | cat -n
echo
echo "Behavioral probe for calendar-date fix:"
TZ=America/Los_Angeles node - <<'NODE'
function computeAgeFromDob(dob) {
if (!/^\d{4}-\d{2}-\d{2}$/.test(dob)) return '';
const birth = new Date(dob);
const today = new Date(2026, 7, 3);
let age = today.getFullYear() - birth.getFullYear();
if (today.getMonth() < birth.getMonth() || (today.getMonth() === birth.getMonth() && today.getDate() < birth.getDate())) age -= 1;
return age >= 0 ? String(age) : '';
}
function computeAgeFromDobLocal(dob) {
if (!/^\d{4}-\d{2}-\d{2}$/.test(dob)) return '';
const year = parseInt(dob.slice(0, 4), 10);
const month = parseInt(dob.slice(5, 7), 10);
const day = parseInt(dob.slice(8, 10), 10);
const birth = new Date(year, month - 1, day);
const today = new Date(2026, 7, 3);
let age = today.getFullYear() - birth.getFullYear();
if (today.getMonth() < birth.getMonth() || (today.getMonth() === birth.getMonth() && today.getDate() < birth.getDate())) age -= 1;
return age >= 0 ? String(age) : '';
}
for (const dob of ['2000-08-04', '2000-08-05', '2000-08-03']) {
console.log({ dob, ageCurrent: computeAgeFromDob(dob), ageLocalOnly: computeAgeFromDobLocal(dob), expectedAge: dob <= '2000-08-04' ? 25 : 24 });
}
NODERepository: nnkogift/dhis2-form-utils
Length of output: 3698
Parse the date as a local calendar date.
new Date(dob) treats the date-only string as UTC midnight, so local date getters can read the prior calendar day west of UTC. When the current day is before the birth date, this can report one extra year of age. Parse the yyyy-mm-dd fields into new Date(year, month - 1, day) instead.
🤖 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 `@components/mui/src/fields/widgets/TextField.tsx` around lines 56 - 65, Update
computeAgeFromDob to parse the validated date components as a local calendar
date using the year, month minus one, and day constructor arguments, replacing
new Date(dob). Preserve the existing age calculation and invalid/future-date
handling.
There was a problem hiding this comment.
Left a non-blocking comment (not approved): Cursor Security Agent completed successfully but still has 1 unresolved medium-severity finding that needs human attention. Cursor Bugbot was not present on this PR; no reviewers were assigned because no eligible non-author reviewers are available.
Sent by Cursor Approval Agent: Pull Request Router and Approver




Summary
Repo-wide cleanup pass following a review against the
coding-standardsskill and fallow's whole-project static analysis (dead code, duplication, complexity, dependency hygiene). Split into four commits, one per phase:chore: remove dead barrel, dedupe form types, and fix dependency hygiene— removes an orphaned barrel inutils/hooks, consolidates aDefaultFormValuetype duplicated acrossuseEventForm/useTrackerForm, extracts repeated magic numbers into named constants, and fixes package.json dependency declarations (missing@dhis2/d2-i18n/@testing-library/reactin the playground, unuseddayjs/mockdatein storybook, misdeclared@dhis2/api-types/@dhis2/data-enginein hooks).refactor: split mui widgets into one-component-per-file— splits mui's 14-componentFields.tsxinto per-domain files, matching the layout already used bydhis2-uiandmantine; resolves a CRITICAL complexity hotspot on the combined file.refactor: dedupe rule-engine interop and simplify dispatch logic— extracts three functions duplicated identically betweencontext.tsandenrollmentContext.ts(fallow's top-ranked duplication target) into a shared module, and replaces two switch-based dispatchers (valueTypeToZod,applyEffect) with declarative lookup tables, resolving fallow's two complexity hotspots.chore: declare playground entry point for fallow and drop dead widget file— adds.fallowrc.jsonwith an explicit entry point so fallow recognizes the DHIS2 App Platform'sd2.config.jsentry convention (previously cascaded into 55 files misreported as dead across the playground); also removes one genuinely orphaned widget file uncovered while verifying the corrected dead-file list.Everything not touched here (playground's flat
hooks//utils//types/folders, cross-adapterD2Fieldduplication, further decomposition ofcontext.ts) was deliberately left as a follow-up — see the review discussion for why.Test plan
pnpm typecheck— all packages passpnpm lint— clean on all touched files (pre-existing unrelated warnings elsewhere untouched)pnpm test— 162/162 unit tests, 80/80 Storybook browser tests passpnpm build— all packages + Storybook build successfullyfallow-gatepre-commit hook passes (verdict: pass, 0 newly-introduced issues) on every commit🤖 Generated with Claude Code
Summary by CodeRabbit