feat(api): DTPR icon composition — symbols, shapes, composed icons, MCP get_icon_url#270
Merged
feat(api): DTPR icon composition — symbols, shapes, composed icons, MCP get_icon_url#270
Conversation
|
Too many files changed for review. ( |
- Add symbol_id to ElementSchema with [a-zA-Z0-9_-] whitelist - Add ShapeTypeEnum + required shape field to CategorySchema - Collapse category_ids: string[] to singular category_id - Drop IconSchema and rule 14 (ICON_URL_EMPTY, ICON_FORMAT_EMPTY) - Cascade singular category_id through validator, REST, MCP, search - Rewrite schema tests for new shape; update all fixtures
- api/src/icons/shapes.ts: SHAPES record with 4 shape templates (circle, hexagon, octagon, rounded-square), getShapeSvgFragment() with parameterized fill/stroke, UnknownShapeError - api/src/icons/color.ts: parseHex, relativeLuminance, contrastRatio, innerColorForShape per WCAG 2.1 (threshold 0.179) - Pure modules, no I/O or Worker globals
- api/src/icons/compositor.ts: composeIcon({ shape, symbolSvg, variant })
produces 36x36 composed SVG; stripOuterSvg rejects BOM/XML-prolog/
leading-comment wrapper at runtime
- Variant matrix: default (outline), dark (inverted), colored (WCAG-picked
inner color)
- Deterministic: pure string composition, no randomness or timestamps
- 19 tests incl. 4 golden-file parity checks (one per shape primitive)
…ory_id - transform-element: emit symbol_id (v1 symbol filename stem) and singular category_id (first ai__* from v1 list); drop icon block - transform-category: emit shape from AI_CATEGORY_SHAPE_MAP (copied from studio/lib/icon-shapes.ts) - port script: copy unique symbol SVGs from app/public/dtpr-icons/symbols into release symbols/ directory; fail on missing source file - Regenerate ai@2026-04-16-beta: 11 categories + 75 elements + 64 unique symbol SVGs Migration test coverage: symbol_id derivation, singular category_id picking, per-shape emission, ELEMENT_NO_SYMBOL warning path
…ntent_hash coverage Build pipeline: - yaml-reader: load release symbols/ directory into SchemaVersionSource - json-emitter: materialize shape + icon_variants onto emitted elements - json-emitter: pre-bake (element x variant) composed SVGs via composeIcon - json-emitter: include sorted symbol + composed-icon sha256 maps in content_hash so byte changes without YAML changes force a new hash Validator rules: - symbol-refs: SYMBOL_NOT_FOUND (with nearest-id fix_hint), SYMBOL_MALFORMED_WRAPPER (BOM / xml prolog / leading comment), SYMBOL_ACTIVE_CONTENT (script, event handlers, external refs, foreignObject) - variant-reserved: RESERVED_VARIANT_TOKEN for context value ids colliding with 'default' or 'dark' - color-contrast: LOW_CONTRAST_CONTEXT_COLOR warning (< 4.5:1) Build output on ai@2026-04-16-beta: 64 symbols + 168 composed icons across 75 elements.
- store/keys.ts: symbolKey(), composedIconKey() - store/r2-loader.ts: loadSymbolSvg, loadComposedIconSvg using cachedText and the existing beta-aware cache options - store/index.ts: inline-bundle fallback for both + singular loadCategory - store/inline-bundles.ts: symbols and composedIcons maps on InlineBundle - scripts/r2-upload.ts: .svg content-type mapping; walker already handles the nested icons/<element_id>/<variant>.svg tree
- GET /api/v2/shapes/:shape.svg — bundled shape primitive, always immutable cache - GET /api/v2/schemas/:version/symbols/:symbol_id.svg — release-pinned symbol from R2 (with inline-bundle fallback); beta caches 1h, stable is immutable - responses.ts: setIconCacheHeaders helper with prebaked flag so Unit 8 can diverge on fallback path - error-handler: Cache-Control: no-store on all error responses so beta's short TTL never poisons the CDN cache on a 4xx - Explicit per-route timeouts for all icon routes (shape, symbol, composed) in app.ts
- GET /api/v2/schemas/:version/elements/:id/icon.svg (default variant) - GET /api/v2/schemas/:version/elements/:id/icon.:variant.svg (both routes served by a single :icon_variant handler due to Hono's literal-dot-before-param handling) - Hot path: R2 point-read of pre-baked bytes with long beta TTL (1h) / stable immutable cache - Miss-fallback: load element + category + symbol, composeIcon on the fly, short TTL (60s); no R2 write-back - icon_miss_fallback observability log on every miss - resolveComposeVariant maps URL variant to ComposeVariant union; unknown variant → 404 with valid_variants list - Byte-parity test confirms build-time and runtime compositors produce identical bytes for the same inputs
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
dtpr-docs | 0fcb527 | Apr 18 2026, 02:02 PM |
- mcp/tools.ts: get_icon_url tool returns { url, content_type, variant,
valid_variants } so MCP clients resolve composed icon URLs without
building paths by hand
- Validates variant against the element's icon_variants; unknown variant
returns error envelope listing valid options
- icon_variants, symbol_id, shape surface automatically via get_element
and list_elements (when explicitly requested in fields); default
list_elements projection stays tight
- Seed fixtures extended with context on ai__decision and a
MaterializedElement shape so MCP responses mirror real release JSON
- brainstorm: 2026-04-17-dtpr-icon-composition-brainstorm - plan: 2026-04-17-002-feat-dtpr-icon-composition-plan (the blueprint this branch implements) - stub: 2026-04-17-001-feat-content-release-terminology-rename (deferred follow-up)
63cca9e to
90fa520
Compare
Element no longer carries an icon field; category_ids/symbol refs collapsed to singular category_id + symbol_id, categories gained shape, and SchemaVersionSource now requires a symbols map. Update types, deriveElementDisplay (iconUrl/iconAlt now come via options, default to HEXAGON_FALLBACK + title), and test fixtures to match, which also unblocks validateDatachain's happy-path runtime check. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
Moves DTPR icon composition into the
api/Cloudflare Workers app as a first-class capability. Structural schema gainssymbol_idon elements andshapeon categories;IconSchemais dropped;category_ids: string[]collapses tocategory_id: string. Build pipeline pre-bakes every(element × variant)composed SVG to R2 — the runtime hot path is a plain R2 point-read with long CDN TTL, and on-the-fly composition remains as a miss-fallback so authors can iterate on new symbols/context colors between builds.New REST endpoints under
/api/v2/:shapes/:shape.svg,schemas/:version/symbols/:symbol_id.svg, andschemas/:version/elements/:id/icon[.variant].svg. New MCP toolget_icon_url. Release content ported via the migration to emit the new fields + copy 64 unique symbol SVGs into the beta release, yielding 168 pre-baked composed SVGs per build. Plan implemented end-to-end in 9 units; 355 tests passing (323 Workers + 32 CLI).Test plan
pnpm --filter api typecheckpassespnpm --filter api testpasses (355 tests)pnpm --filter api schema:build ai@2026-04-16-betaemits 64 symbols + 168 composed icons todist/curlreturnsimage/svg+xmlwith correctCache-ControlandDTPR-Content-Hashheadersicon_miss_fallbacklog line appears when the R2 object is absent and disappears afterr2-upload🤖 Generated with Claude Code