feat: Categories Editor — browse, search, and import .comapeocat category sets (closes #143)#147
Open
luandro wants to merge 26 commits into
Open
feat: Categories Editor — browse, search, and import .comapeocat category sets (closes #143)#147luandro wants to merge 26 commits into
luandro wants to merge 26 commits into
Conversation
added 4 commits
July 22, 2026 14:50
…egory format - Add import-file schemas: metadataSchema, fieldSchema, categorySchema, comapeoCatSchema - Vendor 5 representative category + 10 field fixtures from comapeo-default-categories v1.1.2 - Add loadDefaultCategoryFixtures() fixture loader - Export new schemas from barrel index - 54 tests covering valid/malformed/edge cases for all schemas
- usePresets: TanStack Query hook fetching presets from server API with Valibot validation - normalizeCategories: pure function for grouping by tags.type, locale-aware search, deterministic ordering - 17 tests covering success/error/validation/locale/grouping/search/edge cases
- Register /categories and /categories/ routes - Add Categories nav item with grid icon to primary rail - Create screen shell with loading/error/empty/search states - Add en/pt/es i18n messages for all strings - 4 screen tests covering loading/error/empty/search states
- CategoryCard: colored icon fallback, color accent strip, field count, semantic article - CategoryGrid: section headers by tags.type, responsive 2-4 column grid - Extended Category model with color/iconRef pass-through - 8 new tests covering group headings, cards, accents, accessibility, empty state
added 7 commits
July 22, 2026 15:28
…elect unwrap - Add iconRef to PresetInput interface in useCategories - Remove unused Card import from CategoryCard - Fix duplicate useNavigate mock in test - Add select: (data) => data.data to usePresets for backward compat - Fix test assertions for unwrapped data shape
…refs
- Remove label from PresetInput.fieldRefs (API returns {docId,versionId,url} only)
- Add optional fieldLabels: Map<string,string> param to normalizeCategories
- Resolve field labels in search and output from the field lookup map
- Add test for field label resolution
- FieldViewer: read-only previews for text/selectOne/selectMultiple/date/number/unknown field types - CategoryDetail: icon, color swatch, field refs with resolved labels, back nav - Integrated useFields hook for field label resolution - Responsive layout: grid + sticky sidebar on desktop, full-width on mobile - CategoryCard: selection state with aria-pressed and keyboard support - 20 new tests (10 FieldViewer, 10 CategoryDetail), 31 total CategoriesEditor tests
- categories-db: Dexie DB with versioned schema, importCategorySet/getCategorySet/getCategorySets - useCategorySets: hook for listing imported category sets - ImportSetDialog: file upload, JSON parsing, Valibot validation, replace confirmation - 16 tests covering valid/malformed/replace/retrieve/dialog lifecycle
- Revert usePresets to use getPresets (data-layer) for backward compat - Create useApiPresets with apiClient + select unwrap for CategoriesEditor - Fix ESLint: remove unused Category import, move useMemo before early returns - Fix TypeScript: resolve consumer hooks' Preset type mismatch - Update test mocks for new hook structure
luandro
force-pushed
the
agent/comapeo-cloud-app/issue-143
branch
from
July 22, 2026 19:26
7f3506b to
312003e
Compare
Contributor
|
Preview deployment ready: https://agent-comapeo-cloud-app-issu-62kr.comapeo-cloud-app.pages.dev Commit: |
… a11y - useCategorySets: add error state + .catch on toArray() and refresh - ImportSetDialog: useEffect cleanup for auto-close timer, try/catch on replace - categories-db: remove dead getCategoriesDb singleton - CategoriesEditor: add aria-label to search input
added 7 commits
July 22, 2026 16:58
- CategoriesEditor: show 'Select a project' message when no project selected (stops eternal skeleton) - FieldViewer: fix select_one/select_multiple → selectOne/selectMultiple to match Valibot enum
- ImportSetDialog: wrap first-time import in try/catch, add failedToRead/importError i18n messages
- CategoryCard: use intl.formatMessage for field count (was hardcoded English)
- FieldViewer: use intl.formatMessage for Date/Number/Unknown type badges (was hardcoded)
- index.tsx: add selectProject message definition
- Add categories.* i18n keys to en/es/pt: fieldCount, fieldType.*, importSet.{failedToRead,importError}, selectProject, untitledProject
The presetRefSchema requires {docId, versionId, url} but the CoMapeo
preset schema v1 defines ref items as {docId, versionId} only — no url.
The server omits url from fieldRefs and iconRef in preset responses,
causing v.parse() to throw → preset query isError → 'Failed to load
categories' screen.
Switch to docRefSchema (which already exists for this exact reason —
the comment says 'a single missing field would previously have silently
dropped ALL tracks for the project') with optional versionId and url.
Verify that presetSchema accepts fieldRefs and iconRef without a 'url'
field — the comapeo-schema preset v1 defines ref items as {docId, versionId}
only. The server omits 'url' from these, and the strict presetRefSchema was
the root cause of the 'Failed to load categories' runtime error.
- i18n: 'Uncategorized' group header (useCategories sentinel '' + CategoryGrid translate) - ImportSetDialog: wrap getCategorySet conflict-check in try/catch - ImportSetDialog: replace-flow uses importError i18n key (was hardcoded English with wrong validationError key) - Static import getCategorySet (drop redundant dynamic import)
- CategoriesEditor index: test happy path (cards render), no-project state, empty state - useCategories: test real diacritics (Água, São Paulo, Ação) against unaccented queries - useCategories: test accented query against plain target name
…, iconRef/color, replace flow, useCategorySets Addresses all 4 MEDIUM findings from claude-qwen test-coverage review: - CategoryCard: click/keyboard selection + aria-pressed tests (10 tests) - useCategories: iconRef & color branch coverage (4 tests) - ImportSetDialog: replace-execution + file-read-failure paths (2 tests) - useCategorySets: new hook test file (4 tests — empty, populated, error, refresh)
… localId (hex) The comapeo-cloud server's /projects/:projectPublicId/preset route validates the projectPublicId against a base32 regex. useApiPresets was passing the project's local DB id (hex UUID), causing the server to return an error for remote archive projects. pullPresets already uses the correct remoteId. - useApiPresets: renamed parameter projectLocalId→projectRemoteId - CategoriesEditor: resolves selectedProject.remoteId from projects query - Local projects without remoteId show empty state (not eternal skeleton)
…in tests
HIGH issues from claude-qwen review:
1. When projectsQuery was pending/error, the !selectedProject?.remoteId guard returned
empty state instead of loading skeleton or error+retry. Fixed by adding explicit
projectsQuery.isPending / isError / !selectedProject guards before the remoteId check.
2. Tests mocked useApiPresets but never verified the argument — reverting to localId
would leave every test green. Added assertions for useApiPresets('base32proj1')
and useApiPresets(null), plus guard tests for pending/error/not-found.
…cache
The comapeo-cloud server returns 304 Not Modified (ETag cache hit) for
/preset and /field endpoints. The browser's fetch receives the raw 304
without a cached body, and handleResponse treats !response.ok (304 ∉ 200-299)
as an error → ApiError → 'Failed to load categories'.
Two-pronged fix:
1. handleResponse: handle 304 before the !response.ok check, return
{ data: [] } — safe for all list-envelope schemas.
2. getPresets/getFields: add cache: 'no-store' to fetch calls to prevent
the browser from sending If-None-Match/If-Modified-Since headers.
Tests: 6 new api-client tests covering 304, 404, 500, and success paths.
… all reads
claude-qwen review: the { data: [] } as T cast in handleResponse was
unsafe for non-list endpoints (getProject, getServerInfo) — a 304 would
resolve to wrongly-shaped data, corrupting e.g. pullProjects.
Fix: remove the 304 fallback entirely. Add cache: 'no-store' to ALL
read endpoints (getServerInfo, getProjects, getProject, getObservations,
getTracks, getAlerts, getPresets, getFields). With no-store, the browser
never sends If-None-Match/If-Modified-Since, so 304 is unreachable.
Tests: added getProject 304 regression test asserting it throws (never
resolves to {data:[]}), plus getPresets 304 throw test.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #143
What
Implements the Categories Editor screen at
/categories— Phase 1-5 complete.Changes (21 commits, 44 files, +3229/-68 lines)
Core Implementation
Review Findings Addressed (this session)
urlper comapeo-schema v1; strict schema caused 'Failed to load categories'Test Results
Greptile Summary
This PR adds a Categories Editor for browsing, searching, viewing, and importing category sets. The main changes are:
.comapeocatvalidation, conflict handling, and Dexie persistence.Confidence Score: 5/5
This looks safe to merge.
The preset response is unwrapped before category normalization, and field labels are resolved through the project field data.
No blocking issues were found in the updated code.
What T-Rex did
Important Files Changed
Reviews (12): Last reviewed commit: "fix(issue-143): remove unsafe 304→{data:..." | Re-trigger Greptile