Skip to content

feat(tags): group tags into categories - #685

Merged
alukach merged 2 commits into
mainfrom
feat/tag-categories
Oct 30, 2026
Merged

alukach merged 2 commits into
mainfrom
feat/tag-categories

Conversation

@alukach

@alukach alukach commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Every approved tag now belongs to one of eight categories, and the tag picker groups its list by them. Rebuilt on main after #647 and #648 merged, on top of #648's REST tags API, TagMenu and staged-merge review card.

The tag picker, grouped by category

Approving a suggestion requires a category

(The screenshots predate the rebase onto #648's UI. The grouping and the required category are the same, but the controls around them now look like #648's.)

Categories

The five categories proposed (feature type, methodology, format, domain, temporal frequency) plus three more, because about half of today's tags fit none of the five: place, sensor or platform, and source or project. They're defined in src/lib/tags/categories.ts, in the order the picker shows them.

Category What it says For example
Domain The subject the data is about. agriculture, weather, climate, energy
Feature type What each record represents. field, boundaries, building-footprints, land-cover
Place Where the data covers. spain, germany, united-states, global
Methodology How the data was made. segmentation, forecasting, machine-learning
Sensor or platform The instrument or mission that collected it. sentinel-2, landsat, goes, gpm
Source or project The organization, dataset or project it comes from. fiboa, fields-of-the-world, nasa, usgs
Format How the files are stored. geoparquet, pmtiles, zarr, csv
Temporal frequency How often a value is recorded. time-series, daily, hourly, monthly

BY_CATEGORY in scripts/clean-tags.ts assigns every corpus tag. It follows the cleanup on main: field and boundaries and admin replace field-boundaries and admin-boundaries, which #648's cleanup splits. It also covers the tags main's cleanup added to KEEP and its aliases: goes, metop and gpm as sensors; copernicus, epa, esa, jpss, nasa, neon, nsta, usgs, imerg, mrms and tamsat as sources; csv, iceberg and png as formats; and ground-truth as a feature type. The script's preview lists any corpus tag still without a category under "No category yet", and applying refuses until there are none.

What changes

  • Stored on the tag (sc-{stage}-tags): an approved tag's row gets a category. Products still store plain tag strings, so moving a tag to another category rewrites one row.
  • Approving needs a category: PATCH /api/v1/tags/{tag_id} with pending: false takes a category, and refuses without one (invalid, on category). Undoing an approval clears it. On /admin/tags, a category select sits beside Approve as a new tag, which stays disabled until one is chosen.
  • GET /api/v1/tags lists each approved tag's category to anyone, beside tag_id.
  • Tag picker: "Edit tags" sits on its own line below the chips, so adding a chip doesn't push it, and the open list with it, sideways. The list starts with the product's tags; one checked while the list is open joins them straight away, and stays checked in its category too, so the row just clicked doesn't move. After the product's own tags, the list shows tags under category headings, in category order, each with the category's one-line description, which screen readers hear as the group's description. Uncategorized tags go under "Other". A list with no categories at all, such as local data before the cleanup runs, stays one list. TagMenu sections take an optional description for this.
  • Empty tags: a product carrying an empty tag shows no blank chip, and saving it doesn't submit one.
  • Cleanup script: writes each corpus tag with its category, seeds the frequency tags (hourly, daily, weekly, monthly, yearly), and deletes imported tags the corpus no longer has. It leaves suggested tags alone, pending or approved: products keep them, and their rows, which hold the review record, aren't rewritten or deleted. It also rewrites a product whose only tag is "", which the old comparison missed because [""] and [] join to the same string.

Decisions worth a second look

  • vector and raster are under Format. They're geometry types rather than file formats, but a separate "data type" category for two tags seemed like too much.
  • Judgement calls in the mapping: ai is a methodology next to machine-learning; cloud is a domain, though on some products it may mean cloud storage; census, cer and neb are sources; satellite-imagery, imagery, ground-truth and embeddings are feature types; gpm is a sensor (the mission) while imerg is a source (the dataset); time-series is a temporal frequency.
  • One category per tag, chosen by the reviewing admin rather than the suggester.
  • No way to recategorize an approved tag yet other than the script; PATCH only sets a category while approving. Add it when someone needs it.
  • No category facets on /products yet. That's left for its own PR.

Stories

On this branch's Storybook deploy:

Docs

  • public/llms.txt: the /api/v1/tags line now says each tag comes with its category.
  • docs.source.coop's docs/using-source/create-a-data-product.md still describes picking and suggesting tags accurately; the grouping needs no new instructions.
  • The data-proxy ADRs don't cover tags.

Testing

  • The full jest suite passes (1368 tests). npm run type-check reports only @vercel/analytics/next missing, because the local worktree's node_modules predates feat(analytics): add Vercel Web Analytics #689; it's unrelated to this change.
  • src/lib/operations/tags.test.ts: approving stores the category and refuses a missing or unknown one; listTags shows categories to anyone.
  • src/lib/clients/database/tags.test.ts: tagProps passes each approved tag's category.
  • TagPicker.test.tsx: grouping in category order with "Other", group descriptions, an empty tag left out, and a tag checked while open listed with the product's tags as well as in its category. AdminTagSuggestions.test.tsx: approving waits for a category.
  • scripts/clean-tags.ts --counts ran on a sample counts file and grouped the corpus by category and flagged an uncategorized tag. Its <stage> mode wasn't run against a table.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
source-coop-ui Ready Ready Preview Oct 30, 2026 4:45pm UTC
source-cooperative Ready Ready Preview Oct 30, 2026 4:45pm UTC

Request Review

@claude

claude Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @alukach's task in 16s —— View job


✅ No blocking issues — safe to merge.

I read the operations, DB client, action, TagPicker/TagMenu, the admin card and categories.ts. I didn't run the tests or the type-check. The category is validated against the enum at the trust boundary in updateTag, an invalid or missing category returns invalid on category, and undoing an approval removes category. listTags exposes only tag_id and category to non-admins, so nothing private leaks. The picker's fallback to a single list when no tag has a category looks correct.

Simplify (ponytail)

  • src/lib/tags/categories.ts: TAG_CATEGORY_IDS is TAG_CATEGORIES.map(...) cast to a tuple. This is the standard way to feed z.enum from a const array, so it can stay.
  • src/lib/clients/database/tags.ts (tagProps): the flatMap plus as Record<string, TagCategory> cast could be a plain loop or a reduce. This is cosmetic.
  • src/components/features/products/TagPicker.tsx: the "Other" section is built even when it is empty. TagMenu already drops empty groups, so this is fine as written.

Docs

The PR description covers all three: it names the llms.txt change, says docs/using-source/create-a-data-product.md still holds, and says the data-proxy ADRs don't cover tags. Nothing is missing.


💰 Estimated review cost: $0.17 · 0m15s · 5 turns

Every approved tag belongs to one of eight categories, defined in src/lib/tags/categories.ts. The tag picker lists tags under their category headings, each described under its heading, with uncategorized ones under "Other". Approving a suggestion requires a category, through PATCH /api/v1/tags/{tag_id} with `category` and a category select on the review card; undoing an approval clears it. GET /api/v1/tags lists each tag's category. The cleanup script writes each corpus tag's category from BY_CATEGORY, seeds the frequency tags, refuses to apply while a corpus tag has none, deletes imported tags the corpus no longer has, leaves pending and approved suggestions alone, and rewrites products whose only tag is empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A tag checked with the picker open now joins "On this product" at once instead of on the next open, and stays checked in its category too, so the row just clicked doesn't move from under the cursor. "Edit tags" moves to its own line below the chips, so adding a chip no longer pushes the button, and the open popover anchored to it, sideways.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alukach
alukach deleted the feat/tag-categories branch October 30, 2026 16:49

This branch was successfully deployed

3 active (1 outdated) and 1 inactive (outdated) deployments
Preview – source-cooperative — de72ccbb Deployed Oct 30, 2026 by vercel[bot]
Preview – source-coop-ui — de72ccbb Deployed Oct 30, 2026 by vercel[bot]
Staging-Infra — e9d2ea34 Deployed Oct 19, 2026 by alukach via deploy-cdk #308
Staging — e9d2ea34 Deployed Oct 19, 2026 by alukach via deploy-vercel #308
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Proposed Feature] Product Tags administration

1 participant