Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
205 commits
Select commit Hold shift + click to select a range
48b2744
chore: add Claude Code project config with hooks and MCP servers
BRaimbault Jun 2, 2026
e1f4734
chore: add Claude Code tooling for maps-app
BRaimbault Jul 17, 2026
2c72f01
chore: create and use replica accounts in CI
BRaimbault Jul 19, 2026
929b314
chore: remove arbitrary cy.wait
BRaimbault Jul 19, 2026
e5f6846
chore: update e2eReplicaAccount.js
BRaimbault Jul 20, 2026
690090c
feat: add DATA_FILTERS_CLEAR_ALL action and reducer case
BRaimbault Jun 23, 2026
f364871
feat: expose totalCount and filteredCount from useTableData
BRaimbault Jun 23, 2026
4cb9459
feat: redesign BottomPanel dataTableControls as 36px toolbar
BRaimbault Jun 23, 2026
2748537
feat: zoom map to feature on data table row click
BRaimbault Jun 23, 2026
cf83714
feat: add toolbar, row context menu, and fix highlight persistence
BRaimbault Jul 6, 2026
16f8761
chore: sonarqube fixes
BRaimbault Jul 6, 2026
4ecf34d
chore: update cypress tests
BRaimbault Jul 9, 2026
262dacd
chore: update cypress tests
BRaimbault Jul 10, 2026
d0a2188
chore: sonarqube issues fix
BRaimbault Jul 19, 2026
2d12943
chore: create and use replica accounts in CI
BRaimbault Jul 19, 2026
49ab3c0
chore: sonarqube issues
BRaimbault Jul 19, 2026
f2b3f92
feat: add bidirectional map/table selection sync and collapsible data…
BRaimbault Jul 13, 2026
b2161ea
fix: bump maps-gl
BRaimbault Jul 13, 2026
392805a
chore: sonarqube issues
BRaimbault Jul 13, 2026
20e047b
chore: cypress tests update
BRaimbault Jul 13, 2026
49a1984
chore: sonarqube issues
BRaimbault Jul 13, 2026
cc11f54
chore: fix cypress tests
BRaimbault Jul 13, 2026
6d9fa0b
chore: sonarqube issues fix
BRaimbault Jul 27, 2026
f8c32cb
feat: preserve optionSet on event layer table headers
BRaimbault Jul 13, 2026
20b26b0
feat: compute columnOptions for categorical data table columns
BRaimbault Jul 13, 2026
1ecec00
feat: support array-valued (multi-select) filters in filterData
BRaimbault Jul 13, 2026
4b032a2
feat: add filterByGlobalSearch utility and thread globalSearch throug…
BRaimbault Jul 13, 2026
e4f4ebb
feat: add multi-select dropdown filter to FilterInput
BRaimbault Jul 13, 2026
b703733
feat: add numeric filter syntax help tooltip
BRaimbault Jul 13, 2026
bcb2326
feat: pass columnOptions and optionSet through DataTable to FilterInput
BRaimbault Jul 13, 2026
071c6f2
feat: add global search box to data table toolbar
BRaimbault Jul 13, 2026
da9c11a
feat: wire legend item clicks to pre-populate data table filter
BRaimbault Jul 13, 2026
5dc740c
chore: fix lint/prettier issues in PR3 files
BRaimbault Jul 13, 2026
c4218fe
fix: toolbar polish - clear filters button, search sizing, collapse i…
BRaimbault Jul 14, 2026
09b7087
feat: round out data table filtering with reverse-selection, zoom-to-…
BRaimbault Jul 14, 2026
6a1fcba
revert: remove legend-click-to-filter, deferred to a future PR
BRaimbault Jul 14, 2026
954d42e
chore: sonarqube issues
BRaimbault Jul 14, 2026
f9f22ce
chore: sonarqube issues
BRaimbault Jul 14, 2026
62d0b31
chore: sonarqube issue
BRaimbault Jul 14, 2026
757a19b
fix: handle narrow columns and hide index column
BRaimbault Jul 14, 2026
11604ed
fix: rollback merging range and color columns
BRaimbault Jul 16, 2026
3b282b5
chore: PR clean-up
BRaimbault Jul 16, 2026
faa0a7b
chore: sonarqube issues
BRaimbault Jul 16, 2026
5f49e1b
fix: properly format dropdown numeric values
BRaimbault Jul 16, 2026
bf4c5fa
chore: fix cypress tests
BRaimbault Jul 16, 2026
7e0bc73
chore: cypress test fix
BRaimbault Jul 16, 2026
57a520a
chore: cypress test fix
BRaimbault Jul 16, 2026
f3ecf06
chore: cypress test fix
BRaimbault Jul 17, 2026
3dd5d74
chore: create and use replica accounts in CI
BRaimbault Jul 19, 2026
3ef2c71
chore: update e2eReplicaAccount.js
BRaimbault Jul 20, 2026
31db3e6
feat: add toolbar, row context menu, and fix highlight persistence
BRaimbault Jul 6, 2026
759b54d
feat: add bidirectional map/table selection sync and collapsible data…
BRaimbault Jul 13, 2026
940bd84
fix: toolbar polish - clear filters button, search sizing, collapse i…
BRaimbault Jul 14, 2026
dc0e3d7
feat: round out data table filtering with reverse-selection, zoom-to-…
BRaimbault Jul 14, 2026
ff32bb4
chore: PR clean-up
BRaimbault Jul 16, 2026
9ba799b
feat: add DATA_TABLE_COLUMN_CONFIG_SET reducer action and persistence…
BRaimbault Jul 16, 2026
8d73544
feat: add pure column visibility/order/pin computation
BRaimbault Jul 16, 2026
a7a296f
feat: wire column visibility/pin/order into DataTable rendering
BRaimbault Jul 16, 2026
f4d36dd
feat: add ColumnPicker component for show/hide, pin, and reorder
BRaimbault Jul 16, 2026
cbe7ee2
test: add coverage for dataTableColumnConfig persistence and ColumnPi…
BRaimbault Jul 16, 2026
b456a99
chore: extract i18n strings for ColumnPicker
BRaimbault Jul 16, 2026
a1e51f5
fix: small improvements
BRaimbault Jul 16, 2026
5d495d7
chore: datatable controls refactor
BRaimbault Jul 17, 2026
80ae27c
fix: column picker cleanup
BRaimbault Jul 17, 2026
c0410a6
fix: pr cleanup
BRaimbault Jul 17, 2026
3f25a5e
chore: fix sonarqube issues
BRaimbault Jul 17, 2026
c19f31e
chore: use css module
BRaimbault Jul 17, 2026
bd6ef6c
chore: pr cleanup
BRaimbault Jul 17, 2026
f85a475
chore: pr cleanup
BRaimbault Jul 17, 2026
eac8da3
chore: add retry on delete
BRaimbault Jul 20, 2026
f2cef16
chore: update e2eReplicaAccount.js
BRaimbault Jul 20, 2026
fa7a521
chore: PR wrap-up
BRaimbault Jul 27, 2026
997a788
feat: add bidirectional map/table selection sync and collapsible data…
BRaimbault Jul 13, 2026
525f2e6
feat: add global search box to data table toolbar
BRaimbault Jul 13, 2026
cfa7fee
fix: toolbar polish - clear filters button, search sizing, collapse i…
BRaimbault Jul 14, 2026
3d4ffae
feat: round out data table filtering with reverse-selection, zoom-to-…
BRaimbault Jul 14, 2026
7bf7e33
chore: PR clean-up
BRaimbault Jul 16, 2026
e34991d
fix: ColumnPicker permanently disabled for layer types with synchrono…
BRaimbault Jul 16, 2026
dbfbba1
feat: support tracked entity layers in the data table
BRaimbault Jul 17, 2026
d85ff89
feat: support timeline/split-by-period thematic layers in the data table
BRaimbault Jul 17, 2026
aaeb144
feat: add Legend/Range columns for data-item-styled event layers
BRaimbault Jul 18, 2026
4cc1fc7
feat: add Color/Icon/Group columns for group-set-styled org unit and …
BRaimbault Jul 18, 2026
b73a3e9
feat: add Color column for GeoJSON URL and tracked entity layers
BRaimbault Jul 18, 2026
2dff72e
chore: pr cleanup
BRaimbault Jul 19, 2026
7cbe705
chore: sonarqube issues
BRaimbault Jul 19, 2026
c161c92
chore: fix cypress test
BRaimbault Jul 20, 2026
e543690
fix: datatable performance optimisation
BRaimbault Jul 21, 2026
d2b2cd6
chore: PR clean-up
BRaimbault Jul 23, 2026
fcb310a
chore: sonarqube issue
BRaimbault Jul 23, 2026
b4d63ca
chore: PR clean-up
BRaimbault Jul 23, 2026
fcb4fc0
fix datatable support geometry+multigeometry mix
BRaimbault Jul 23, 2026
a95c172
fix: coherce numeric valueTypes in TE layer for datatable
BRaimbault Jul 23, 2026
b4fae37
fix: no drilling and profile for TE layer
BRaimbault Jul 23, 2026
36fbc00
fix: cancel on resize issues
BRaimbault Jul 23, 2026
da30838
fix: raceediting filters/columns while an event layer's data is still…
BRaimbault Jul 23, 2026
a5028aa
fix: crash + dead-code bug in parseLayerConfig
BRaimbault Jul 23, 2026
7fe8765
fix: header titles alignement
BRaimbault Jul 23, 2026
b0ff4e0
feat: expose spatialSupport from system info for event clustering
BRaimbault Jul 23, 2026
93b2e44
feat: expose spatialSupport from system info in dashboard plugin
BRaimbault Jul 23, 2026
cdf5b4b
feat: thread spatialSupport into event layer loader call sites
BRaimbault Jul 23, 2026
69b73f1
feat: gate server-side event clustering on backend spatial support
BRaimbault Jul 23, 2026
da52d44
feat: add Redux action for forcing client-side event clustering
BRaimbault Jul 23, 2026
7df1cae
test: confirm forceClientCluster is never saved to favorites
BRaimbault Jul 23, 2026
218761f
feat: reload server-clustered event layers when forceClientCluster is…
BRaimbault Jul 23, 2026
95e0f8c
feat: bypass server-cluster decision when forceClientCluster is set
BRaimbault Jul 23, 2026
de36edb
fix: allow opening the data table on a server-clustered event layer
BRaimbault Jul 23, 2026
ef42b65
fix: stop hard-erroring the data table for server-clustered event layers
BRaimbault Jul 23, 2026
d21b1dd
feat: add in-table action to show event details for a clustered layer
BRaimbault Jul 23, 2026
2369c2d
feat: wire the show-event-details action into the data table
BRaimbault Jul 23, 2026
facde24
fix: don't mark a server-clustered event layer's table data as ready
BRaimbault Jul 23, 2026
e5dbe51
fix: show the loaded (capped) event count in the legend, not the raw …
BRaimbault Jul 23, 2026
2a1f552
chore: PR clean-up
BRaimbault Jul 23, 2026
8634fb2
refactor: extract nested ternaries flagged by SonarQube
BRaimbault Jul 23, 2026
84c1c9b
fix: PR clean-up
BRaimbault Jul 23, 2026
1f3ea3a
fix: PR clean-up
BRaimbault Jul 24, 2026
f00285c
chore: sonarqube fix
BRaimbault Jul 24, 2026
f889f5d
feat: add bidirectional map/table selection sync and collapsible data…
BRaimbault Jul 13, 2026
9ea8a24
fix: toolbar polish - clear filters button, search sizing, collapse i…
BRaimbault Jul 14, 2026
d792fc2
feat: round out data table filtering with reverse-selection, zoom-to-…
BRaimbault Jul 14, 2026
f93b091
fix: rollback merging range and color columns
BRaimbault Jul 16, 2026
ff7e87c
feat: hierarchical drill-down filter for date/time data table columns
BRaimbault Jul 24, 2026
bb9b605
feat: hierarchical drill-down filter for org unit data table columns
BRaimbault Jul 24, 2026
ac29924
chore: PR clean-up
BRaimbault Jul 25, 2026
11dc96b
chore: GroupFilterPopover refactor
BRaimbault Jul 25, 2026
6910c72
chore: useGroupFilterInput refactor
BRaimbault Jul 25, 2026
1a53993
fix: tracked entity datatable values resolution
BRaimbault Jul 25, 2026
8f3052d
chore: cypress tests fix
BRaimbault Jul 25, 2026
06bbfc5
chore: fix cypress tests
BRaimbault Jul 27, 2026
23c048a
chore: PR round-up
BRaimbault Jul 27, 2026
796eac2
chore: PR clean-up
BRaimbault Jul 27, 2026
6a6827e
fix: resolve org unit names using display-name setting and drop dupli…
BRaimbault Jul 27, 2026
0a1fd0d
fix: populate org unit hierarchy columns for rows without coordinates
BRaimbault Jul 27, 2026
5586866
chore: add tests
BRaimbault Jul 27, 2026
7996d8e
fix: scope selection reducer's DATA_TABLE_TOGGLE clear to its own layer
BRaimbault Jul 28, 2026
c92e674
feat: rewrite dataTable reducer to multi-layer state {openIds, combin…
BRaimbault Jul 28, 2026
fc6c16a
feat: update all state.dataTable consumers for the new object shape
BRaimbault Jul 28, 2026
7f50f06
feat: add tab bar to BottomPanel for multiple open data table layers
BRaimbault Jul 28, 2026
7fffd2b
feat: add Combined tab with join-level selector and layer picker
BRaimbault Jul 28, 2026
63dbad7
feat: add spatialJoin utility using existing @turf/boolean-point-in-p…
BRaimbault Jul 28, 2026
e2db76a
feat: add useCombinedTableData hook (org unit, parent org unit, spati…
BRaimbault Jul 28, 2026
a9fb3ed
feat: add CombinedDataTable component and wire into BottomPanel
BRaimbault Jul 28, 2026
d261147
fix: resolve org unit names in Combined view and reset stale combined…
BRaimbault Jul 28, 2026
2d364de
fix: fall back to feature id when orgUnitId is absent in Combined vie…
BRaimbault Jul 28, 2026
c2597ea
feat: extend feature/selection state with additive crossLayerIds [DHI…
BRaimbault Jul 28, 2026
d0608a3
feat: expose per-row cross-layer feature ids from useCombinedTableDat…
BRaimbault Jul 28, 2026
505c872
feat: reuse buildRowCells/filterData/compareRows for Combined sorting…
BRaimbault Jul 28, 2026
2e1bc05
feat: add row selection and hover-highlight to CombinedDataTable [DHI…
BRaimbault Jul 28, 2026
6152d51
refactor: share rendering/interaction architecture between DataTable …
BRaimbault Jul 28, 2026
31076fc
refactor: share cell value formatting between DataTable and CombinedD…
BRaimbault Jul 28, 2026
ab6d63f
feat: add pinned columns and session-only column picker to CombinedDa…
BRaimbault Jul 28, 2026
0ffc59b
fix: forward crossLayerIds highlights to every layer they name in Map…
BRaimbault Jul 28, 2026
392c28e
feat: add CombinedTableContextMenu with zoom and drill up/down [DHIS2…
BRaimbault Jul 28, 2026
9c4e345
fix: close remaining crossLayerIds parity gaps found in fresh-context…
BRaimbault Jul 28, 2026
759aca0
fix: DATA_TABLE_TOGGLE no longer resets combinedView/joinConfig on la…
BRaimbault Jul 28, 2026
2c133d5
fix: avoid colon in the spatial join option label (breaks i18next-sca…
BRaimbault Jul 28, 2026
5c29d0b
feat: replace data table tabs with a layer selector dropdown, add a m…
BRaimbault Jul 28, 2026
0934dcc
feat: list every eligible layer in the data table dropdown, not just …
BRaimbault Jul 28, 2026
f9b83fe
feat: add applyAggregation utility and combined-join aggregation type…
BRaimbault Jul 28, 2026
639dcbb
feat: add the combinedTableRef layer type, wired to orgUnitLoader/Org…
BRaimbault Jul 28, 2026
b2ad60a
feat: add hideStyleTab to OrgUnitDialog and a control to open/edit th…
BRaimbault Jul 28, 2026
217c283
feat: rework Combined joins around a reference org unit set [DHIS2-20…
BRaimbault Jul 28, 2026
f6f4ca8
feat: persist combinedJoinConfig on the reference layer [DHIS2-20543]
BRaimbault Jul 28, 2026
8825a1d
fix: close Combined join gaps found in fresh-context review [DHIS2-20…
BRaimbault Jul 28, 2026
43f0905
fix: merge Earth Engine's aggregation values into Combined joins [DHI…
BRaimbault Jul 28, 2026
0992e16
fix: various improvements
BRaimbault Jul 29, 2026
a6eb9d8
fix: combined table persistance
BRaimbault Jul 29, 2026
4079ce3
chore: simplify Combined join/column config persistence
BRaimbault Jul 30, 2026
67f1bfe
chore: PR clean-up
BRaimbault Jul 30, 2026
6ff4ccf
chore: dropdown clean-up
BRaimbault Jul 30, 2026
b833689
fix: close data table filter popovers on Escape
BRaimbault Jul 30, 2026
2b286b2
fix: data table layer selector reliability fixes for Combined and no-…
BRaimbault Jul 30, 2026
ab6fa1d
fix: hide technical id/level/geometry columns by default, lowercase "…
BRaimbault Jul 30, 2026
8625247
feat: default Combined aggregation/org units intelligently, warn on l…
BRaimbault Jul 30, 2026
dc85dc6
fix: small improvements
BRaimbault Jul 30, 2026
a2e88e9
feat: add hidden Earth Engine per-band columns to the single-layer da…
BRaimbault Jul 30, 2026
b28cbee
feat: add hidden Earth Engine per-band columns to the Combined data t…
BRaimbault Jul 30, 2026
b44bd9e
chore: remove comments
BRaimbault Jul 30, 2026
a4fce0d
refactor: tag Combined value dataKeys with a kind (value/count/category)
BRaimbault Jul 30, 2026
a9b8149
feat: add categorical/count Combined value columns for Facility/OrgUn…
BRaimbault Jul 30, 2026
a345cd9
feat: add categorical/numeric/count Combined value columns for Event …
BRaimbault Jul 30, 2026
3c1fc87
feat: add count-only Combined value column for TrackedEntity layers
BRaimbault Jul 30, 2026
dba1187
feat: compute count/category row values and headers in the Combined t…
BRaimbault Jul 30, 2026
83aa44e
feat: add Count/Percentage selector for categorical Combined join col…
BRaimbault Jul 30, 2026
a7d961e
fix: exclude non-numeric Event values from Combined value-column aggr…
BRaimbault Jul 30, 2026
cbfccd4
chore: PR clean-up
BRaimbault Jul 31, 2026
9ddcf2b
chore: PR clean up
BRaimbault Jul 31, 2026
4c95eff
chore: en.pot update
BRaimbault Aug 3, 2026
b2127b9
fix: consistent default sorting
BRaimbault Aug 3, 2026
a779e28
fix: don't clear the org unit hierarchy filter on a no-match search
BRaimbault Aug 3, 2026
7428958
fix: match numeric, date, and org-unit columns in data table global s…
BRaimbault Aug 3, 2026
a39bbee
fix: keep feature selection when opening or closing the data table
BRaimbault Aug 3, 2026
d700cff
fix: keep fixed period columns in thematic timeline data table alongs…
BRaimbault Aug 3, 2026
756d6db
fix: avoid stale period label for timeline columns in the data table …
BRaimbault Aug 3, 2026
4c7969f
fix: support timeline/split-by-period thematic layers in the combined…
BRaimbault Aug 3, 2026
74a6416
fix: apply thousands separator to rows count
BRaimbault Aug 4, 2026
0d03309
fix: apply per-layer filters in the combined data table and flag when…
BRaimbault Aug 4, 2026
c002053
feat: add collapsible rows and consistent styling to the combine-laye…
BRaimbault Aug 4, 2026
5d2e691
fix: pin data table row highlight and remove drill up/down
BRaimbault Aug 4, 2026
baad9ab
fix: add warning when event layer is server clustered
BRaimbault Aug 4, 2026
a6ab462
fix: allow org-unit join to use reference org units with no geometry
BRaimbault Aug 4, 2026
d510af1
fix: performance optimisation
BRaimbault Aug 5, 2026
f37cb9e
chore: fix tests
BRaimbault Aug 5, 2026
8875121
chore: fix sonarqube issue
BRaimbault Aug 6, 2026
f49e3c7
fix: make org-unit and date hierarchy filters work in the combined table
BRaimbault Aug 24, 2026
6f9b647
fix: preserve data table tabs and selection across close/reopen
BRaimbault Aug 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .claude/agents/community-post-writer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: community-post-writer
description: Drafts DHIS2 Community of Practice release-announcement posts. Invoked by the community-post skill once it has gathered version/tickets/tone/prerequisites — not a general-purpose writer, only this one format.
tools: Read, WebFetch, Write
---

You draft release-announcement posts for the DHIS2 Community of Practice forum (community.dhis2.org). Your audience is DHIS2 admins, health-program staff, and analysts — not developers. Never explain implementation details, code, or architecture; explain what changed and why it's useful to someone running or using a DHIS2 instance.

Match the house style exactly, based on real published posts:

- Opening line, verbatim pattern: "Dear DHIS2 Community, We are excited to announce the release of **[App] [version]**." (a seasonal greeting before this is fine if the user supplies one, e.g. "Happy new year!").
- Per-app compatibility sentence, verbatim pattern: "The [App] app is on continuous release, compatible with [version] and above. You can download the new release from [DHIS2 App Hub](link) or test it out on [DHIS2 Play](link)." For maps: App Hub link `https://apps.dhis2.org/app/ad3a9d16-e56f-48a9-a9ed-b906d5646e74`, Play link `https://play.im.dhis2.org/dev/apps/maps`.
- Structure: intro (bolded version) → one heading per feature/section, each with a concrete bullet list of what changed and/or prerequisites, a screenshot placeholder, a Jira link anchor-texted literally "Jira" (never link to a GitHub PR — Jira is the house convention) → an optional data-source/attribution section if external datasets are involved → closing → signature.
- Closing: "Thank you for your continuous support!" or a feature-specific variant if one fits better.
- Signature: "Best regards, [Name], [Title]" — ask if not supplied; both a PM voice and a developer voice are real precedents.
- Title format: "[App] v[X.Y.Z] is now available - [feature summary]" or "[App] version [X.Y.Z] is now available - [feature summary]" — either is fine. Drop the version entirely only for a genuinely cross-app announcement.
- Screenshots: leave a clear placeholder marker per feature section. Never fabricate or describe a fake screenshot — the user supplies real images.
- Never invent a "leave a comment" or other call-to-action — none of the real examples use one; let engagement happen organically.
- Tone: warm but factual, accessible language, no unexplained jargon, technical enough to be precise about what a dataset/feature actually is when that matters (e.g. resolution, update frequency) but never implementation-level.

You'll be given the gathered facts (version, features/tickets, tone lever, prerequisites, whether an urgent update note is needed, who's signing) by the skill that invoked you. If something essential is missing, say so rather than inventing it.
22 changes: 22 additions & 0 deletions .claude/agents/docs-writer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
name: docs-writer
description: Writes/updates maps-app's published end-user documentation (docs/src/*.md) and rebuilds docs/maps.md. Invoked by the docs-update skill once the affected doc file(s) are identified.
tools: Read, Write, Bash
---

You write maps-app's published end-user manual (`docs/src/*.md`, built into `docs/maps.md`). Your audience is DHIS2 admins and analysts using the Maps app — not developers reading source code. Plain instructional prose: what a feature does and how to use it, not why it was built or how it's implemented.

Match the existing conventions exactly:

- `> **Note**` blockquote for callouts.
- `{ #anchor_id }` attribute IDs only on new top-level (`##`) headings, for stable cross-linking — not needed on sub-bullets.
- Images referenced as `![](../resources/images/xxx.png)` (relative path from `docs/src/` — the build script rewrites this to `resources/images/xxx.png` for the generated `docs/maps.md`, don't do that rewrite yourself). Follow the existing `maps_<feature>_<subcontext>.png` naming convention, where `<subcontext>` mirrors the dialog tab name (DATA/PERIOD/STYLE/FILTER/ORG_UNITS/RELATIONSHIPS) when relevant.
- No alt text on images anywhere in the existing docs — match that (empty `![]()`).

Workflow:

1. Edit the identified `docs/src/NN-topic.md` file(s) — never hand-edit `docs/maps.md` directly, it's fully generated and will be overwritten.
2. Run `yarn docs:build` (chains a `docs:format` prettier pass over `docs/src/*.md`, then regenerates `docs/maps.md`).
3. Report back which files changed, including the regenerated `docs/maps.md` — the skill that invoked you decides whether/when to commit.

If the change affects something shown in an existing screenshot, say so explicitly (e.g. "the STYLE tab screenshot at `maps_thematic_layer_dialog_STYLE.png` may need recapturing") rather than fabricating a new image — real screenshots are actual app captures, never synthesize one.
51 changes: 51 additions & 0 deletions .claude/agents/spec-writer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
name: spec-writer
description: Turns a ticket, interview answers, and codebase-exploration findings into a polished, self-contained spec document. Invoked by the spec-from-ticket skill after the interactive interview is already done in the main session — does not conduct the interview itself.
tools: Read, Write, Grep, Glob
---

You write engineering specs meant to be read cold, later, by another developer, an architect, or a different AI-agent session — not by the person who just finished interviewing the user. Precise and self-contained: name concrete files/interfaces, state scope boundaries explicitly, don't leave anything implicit that the reader would otherwise have to reconstruct from a conversation they weren't part of.

You will be given: the ticket content, the answers from an interview already conducted with the user, and findings from an Explore subagent that already scanned the codebase. Do not re-interview or re-explore — author from what you're given, using `Read`/`Grep`/`Glob` only to double-check specific file paths or confirm a detail before committing it to the spec, not to redo the exploration.

Write to `.claude/specs/<TICKET-ID>-<slug>.md` (or `.claude/specs/<slug>.md` if there's no ticket), following this structure:

```markdown
---
ticket: DHIS2-XXXXX # or "none"
status: draft
created: YYYY-MM-DD
---

# <Ticket title, plain language>

Implements [DHIS2-XXXXX](https://dhis2.atlassian.net/browse/DHIS2-XXXXX)

<!-- omit this link line entirely if there's no ticket -->

## Problem statement

## Acceptance criteria

## Scope

### In scope

### Out of scope

## Assumptions

## Affected files / interfaces

## Implementation plan

Numbered, each step small and file-scoped enough to plausibly be one commit.

## Verification

Concrete and runnable — exact test commands, exact manual-check steps.

## Open questions
```

If re-writing an existing spec for the same ticket, update it in place — never create a `-2` file. If the interview/exploration left a genuine unresolved question, put it under "Open questions" rather than guessing — don't silently resolve something you weren't actually told.
19 changes: 19 additions & 0 deletions .claude/agents/test-scenario-writer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
name: test-scenario-writer
description: Drafts the "Manual testing" section of a maps-app PR body for internal QA/testers. Invoked by the manual-test-scenarios skill once ticket IDs and Netlify preview links are known.
tools: Read, Write
---

You write the "Manual testing" section of a maps-app PR description. Your audience is an internal, technically-comfortable tester who knows the app well but wasn't part of implementing this specific change — not a fully non-technical end user, and not a fellow developer reading code. Trust them with a working link and a screenshot; don't write click-by-click prose instructions.

Match this real, established convention exactly:

- Header: `### Manual testing`.
- Opening line, once, verbatim pattern: `Netlify: https://pr-<PR#>.maps.netlify.dhis2.org/ + Instance: https://dev.im.dhis2.org/maps-app-42-3`.
- One bullet per Jira ticket, reusing the exact `[TICKET-ID](https://dhis2.atlassian.net/browse/TICKET-ID): <ticket title>` link already used elsewhere in the PR body (under "Implements") — the ticket grouping _is_ the scenario grouping, not a separate numbered list.
- Under each ticket bullet, a nested "Test map(s)" list: `[<UID> - main](<netlify-url>#/<UID>)`, keyed by a real DHIS2 map/dashboard-item UID on the pinned Netlify preview. When the "Dashboard tested" checklist item applies, add a paired link with `?interpretationId=<id>` labeled `- plugin` right next to the `- main` one.
- A screenshot per scenario block (`<img height="250" alt="image" src="..." />`) as the visual "expected result" — never write textual "expected result:" prose instead.
- Use `&nbsp;` as a spacer line between ticket blocks (cosmetic, matches the real convention).
- No "Scenario 1 / Scenario 2" labeling, no numbered click-path steps.

You'll be given the ticket list, the Netlify PR number(s)/instance URL, and real map/dashboard UIDs by the skill that invoked you — never invent a UID or fabricate a screenshot. If a UID or screenshot wasn't supplied for a ticket, leave that part as an explicit placeholder and say so, don't skip the bullet silently.
62 changes: 62 additions & 0 deletions .claude/commands/sonarqube-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# SonarQube Issue Resolution Workflow

Fix SonarQube quality gate issues for the current branch's PR, fetching issues directly from the SonarCloud API and addressing them in priority order.

**Note**: this project (`dhis2_maps-app` on SonarCloud) is public, so every call below works anonymously — no `SONAR_TOKEN` needed. There is no local scanner/CI step to run — analysis happens automatically server-side on every push (SonarCloud "Automatic Analysis"), so there's no local "publish" step, just push and wait.

## Instructions

### 1. Identify the PR

```bash
gh pr view --json number,title -q '"#\(.number) \(.title)"'
```

No `gh`/`GH_TOKEN` set up yet? Use the public GitHub REST API instead:

```bash
git remote get-url origin # → parse org/repo
curl -s "https://api.github.com/repos/<org>/<repo>/pulls?state=open&head=<org>:$(git rev-parse --abbrev-ref HEAD)" \
| jq -r '.[0].number'
```

### 2. Fetch and prioritize issues

```bash
curl -s "https://sonarcloud.io/api/issues/search?componentKeys=dhis2_maps-app&pullRequest=<pr-number>&resolved=false&ps=100" \
| jq -r '.issues[] | "\(.severity) - \(.type) - \(.message) - \(.component):\(.line)"' | sort
```

Fix order: **BLOCKER → CRITICAL → MAJOR → MINOR → INFO**, and within a severity, BUG before CODE_SMELL. Group by rule — fix every instance of the same rule together.

Create a todo list (TodoWrite) with every issue before starting fixes, so progress stays visible.

### 3. Fix in priority order

For each issue: read the file for context, understand what's being flagged and why, apply the minimal fix that addresses it — don't refactor beyond what's reported.

### 4. Test after each batch

After every 3-5 related fixes:

```bash
yarn lint && yarn test
```

Fix regressions immediately rather than accumulating unverified changes.

### 5. Push and let Automatic Analysis catch up

There's no local scanner to run. Push the commit, then re-poll the PR-scoped issues endpoint from step 2 — SonarCloud's GitHub App re-analyzes automatically on push, usually within a couple of minutes. Poll every 15-20s, capped at ~5 minutes so a stuck webhook doesn't hang the workflow.

### 6. Done when

- All todos completed
- `yarn lint && yarn test` pass
- The PR-scoped issues query returns none of the issues you fixed

## Troubleshooting

- **No matching PR** — confirm the branch has an open PR (`gh pr list` or the GitHub UI).
- **Issues still show after pushing** — re-analysis can lag a few minutes; re-poll rather than assuming the fix didn't take. If issues persist past ~5 minutes, check the PR's checks tab for a failed analysis run.
- **`gh` not authenticated** — not required; all the calls above work anonymously against this public repo.
23 changes: 23 additions & 0 deletions .claude/hooks/post-edit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -uo pipefail

input=$(cat)
file=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty')
[ -n "$file" ] && [ -f "$file" ] || exit 0

cd "${CLAUDE_PROJECT_DIR:-.}" || exit 0

case "$file" in
*.js | *.jsx | *.css | *.json | *.md | *.yml | *.yaml) ;;
*) exit 0 ;;
esac

output=$(yarn d2-style apply "$file" 2>&1)
status=$?

if [ "$status" -ne 0 ] || printf '%s' "$output" | grep -q '\[warn\]'; then
notes=$(printf '%s\n' "$output" | grep -vE '^\$ |^yarn run|^Done in|^info Visit|^\s*$')
jq -n --arg ctx "$notes" '{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: $ctx}}'
fi

exit 0
27 changes: 27 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"permissions": {
"allow": [
"mcp__grep__*",
"mcp__plugin_context7_*",
"mcp__plugin_chrome-devtools-mcp_*"
]
},
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/post-edit.sh"
}
]
}
]
},
"enabledPlugins": {
"chrome-devtools-mcp@claude-plugins-official": true,
"context7@claude-plugins-official": true
},
"enabledMcpjsonServers": ["grep"]
}
59 changes: 59 additions & 0 deletions .claude/skills/branch-update/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
name: branch-update
description: Bring the current feature branch up to date with master by merging (not rebasing) origin/master in — this team's real convention — then resolve any conflicts. Creates a merge commit, so only run when the user explicitly asks; notice and mention a stale branch, don't auto-run this.
disable-model-invocation: true
---

# Branch update

This repo's real convention is **merge master into the feature branch**, repeatedly, during development — not rebase. The whole branch gets squash-merged into master via the PR at the end, so the intermediate merge commits never land on master's mainline; they only exist in the feature branch's own history. Don't rebase, and don't "clean up" these merge commits afterward — they're the norm, confirmed by real history (e.g. `Merge remote-tracking branch 'origin/master' into chore/setup-claude`).

This skill creates a merge commit. `CLAUDE.md`'s "don't stage or commit unless explicitly asked" applies to merge commits too — that's why this skill requires explicit invocation. If you notice a branch is stale, say so; don't run this unasked.

## Steps

1. **Fetch.**
```
git fetch origin master
```
2. **Check how stale you actually are** before merging blindly:
```
git log --oneline HEAD..origin/master
git log --oneline origin/master..HEAD
```
Skip the merge entirely if there's nothing new to bring in.
3. **Merge.**
```
git merge origin/master
```
4. **No conflicts** — done. Mention the new merge commit exists; pushing it is a remote write and per this repo's universal rule needs its own explicit, in-the-moment ask, same as any other push.
5. **Conflicts** — see below. Never resolve by blindly taking "ours" or "theirs" wholesale; read both sides first.

## Resolving conflicts

For each conflicted file:

1. Read the full conflict region plus enough surrounding context to understand _why_ each side changed it — not just what the diff lines say. `git log -p` on the conflicting commits from each side shows intent.
2. Prefer the minimal resolution that preserves _both_ changes' purpose — e.g. two additions to the same list/switch/reducer usually both belong, even when git can't auto-merge the surrounding lines.
3. After resolving, remove the conflict markers completely and re-read the result as if reviewing someone else's diff — a resolution that merges syntactically but silently drops one side's behavior is worse than an open conflict.
4. Run the touched files' tests (`npx jest <file>`) plus `yarn d2-style check <file>` on every file you resolved, then the full `yarn lint && yarn test` before considering the merge done.

### When to stop and ask instead of resolving

Stop and hand back to the user — don't guess — when a conflict is a genuine business-logic collision, not line-adjacency noise: both sides changed the _behavior_ of the same function/condition in ways that don't obviously compose (one side changed a threshold, the other changed the formula it feeds into; one side removed a code path the other just extended). Signs it's this kind, not a mechanical one:

- Resolving it requires deciding which behavior is "more correct," not just how to combine two edits.
- The two sides touch the same logic for unrelated reasons (different tickets), and combining them isn't obviously safe without domain knowledge you don't have.

In that case, **leave the conflict markers in place** — don't `git merge --abort` unless the user asks you to. Run `git status` to show which files are still unresolved, describe what each side was trying to do, and ask the user how to reconcile them. Aborting loses the "you got this far" context for no benefit once you've already identified the ambiguity; leaving it in progress preserves both sides' intent for the user to inspect directly.

## Done when

- `git status` shows a clean merge (no unresolved paths).
- `yarn lint && yarn test` pass.
- The merge commit's message is left as git's default (`Merge branch 'master' into <branch>` / `Merge remote-tracking branch 'origin/master' into <branch>`) — don't rewrite it to Conventional Commits format. These merge commits are exempt: they never land on master's mainline after the eventual squash-merge, and this team's real history confirms the default message is what's actually used.

## Related

- `commit-and-pr-messages` — for the _feature_ commits this branch carries, not the merge commit created here.
- `pr-chain` — has its own, more involved version of this problem once a stack member actually squash-merges (a plain merge stops working at that point; see that skill's `references/squash-merge-sync.md`).
Loading
Loading