diff --git a/src/components/XahauRoadmap.astro b/src/components/XahauRoadmap.astro index 7b7ac09..7459210 100644 --- a/src/components/XahauRoadmap.astro +++ b/src/components/XahauRoadmap.astro @@ -45,6 +45,35 @@ const tr = (v: unknown): string => { return '' } +/** + * Card tag text. + * + * Items carry vocabulary *keys* (`phase`, `note`) rather than per-locale + * strings, so a wording change happens once in `labels..tags` instead + * of once per item per language. Renders " · "; `note` is + * optional. `tag` stays supported as a free-form escape hatch for one-offs. + * + * An unknown key is a build error, not a silently blank tag — the fallback + * locale is checked too, so a key missing only from a translation still + * renders (in the default locale) rather than breaking the page. + */ +const tagFor = (it: RoadmapItem): string => { + if (!it.phase) return it.tag ? tr(it.tag) : '' + + const pick = (kind: 'phases' | 'notes', key: string): string => { + const here = data.labels[lang]?.tags?.[kind]?.[key] + const base = data.labels[defaultLocale]?.tags?.[kind]?.[key] + if (here ?? base) return (here ?? base) as string + throw new Error( + `roadmap.json: item "${it.id}" uses ${kind === 'phases' ? 'phase' : 'note'} ` + + `"${key}", which is not defined in labels.${defaultLocale}.tags.${kind}.`, + ) + } + + const phase = pick('phases', it.phase) + return it.note ? `${phase} · ${pick('notes', it.note)}` : phase +} + const qToIdx = (q: string) => { const [y, qx] = q.split('-Q') return Number(y) * 4 + (Number(qx) - 1) @@ -209,7 +238,7 @@ const fmtItemPeriod = (item: RoadmapItem): string => { return (
- {it.tag &&
{tr(it.tag)}
} + {tagFor(it) &&
{tagFor(it)}
}
{tr(it.title)}
{it.description &&
{tr(it.description)}
} @@ -245,7 +274,7 @@ const fmtItemPeriod = (item: RoadmapItem): string => {
- {it.tag &&
{tr(it.tag)}
} + {tagFor(it) &&
{tagFor(it)}
}
{tr(it.title)}
{it.description &&
{tr(it.description)}
}
diff --git a/src/data/roadmap.json b/src/data/roadmap.json index 60bdcd3..cc00f0e 100644 --- a/src/data/roadmap.json +++ b/src/data/roadmap.json @@ -1,6 +1,5 @@ { - "$comment": "Xahau roadmap source of truth. Edit this file to add/update/remove items. Committed JSON is rendered at build time by src/components/XahauRoadmap.astro — no runtime JS required.", - + "$comment": "Xahau roadmap source of truth. Edit this file to add/update/remove items. Committed JSON is rendered at build time by src/components/XahauRoadmap.astro — no runtime JS required. Card tags come from `labels..tags`: set `phase` (and optionally `note`) on an item instead of writing the tag text per locale.", "meta": { "updated": "2026-04-19", "window": { @@ -14,7 +13,6 @@ "locales": ["en", "es", "pt-BR"] } }, - "labels": { "en": { "title": "Xahau Roadmap", @@ -54,6 +52,33 @@ "partnership": "Partnership", "launch": "Launch / public good" }, + "tags": { + "$comment": "Card tag vocabulary. Items reference these by key via `phase` / `note`; the card renders \" · \". Edit a word here and it changes everywhere it is used. A `note` is written to agree with the `phase` it follows (gender/number), so check before reusing one under a new phase.", + "phases": { + "feature": "Feature", + "ongoing": "Ongoing", + "live": "Live", + "ga": "GA", + "pilot": "Pilot", + "launch": "Launch", + "partnerships": "Partnerships", + "enterprise": "Enterprise", + "event": "Event" + }, + "notes": { + "activated": "Activated", + "ready-for-amendment": "ready for amendment", + "in-development": "in development", + "alpha": "in alpha development", + "beta": "in beta development", + "audits-optimisations": "audits + optimisations", + "remittance": "remittance", + "payments": "payments", + "public-good": "public good", + "quarterly": "quarterly", + "enterprise-devs": "enterprise + devs" + } + }, "foot": "Directional - subject to change - xahau.network" }, "es": { @@ -94,6 +119,33 @@ "partnership": "Alianza", "launch": "Lanzamiento / bien público" }, + "tags": { + "$comment": "Card tag vocabulary. Items reference these by key via `phase` / `note`; the card renders \" · \". Edit a word here and it changes everywhere it is used. A `note` is written to agree with the `phase` it follows (gender/number), so check before reusing one under a new phase.", + "phases": { + "feature": "Función", + "ongoing": "Continuo", + "live": "En vivo", + "ga": "GA", + "pilot": "Piloto", + "launch": "Lanzamiento", + "partnerships": "Alianzas", + "enterprise": "Empresarial", + "event": "Evento" + }, + "notes": { + "activated": "activada", + "ready-for-amendment": "lista para enmienda", + "in-development": "en desarrollo", + "alpha": "en desarrollo alfa", + "beta": "en desarrollo beta", + "audits-optimisations": "auditorías y optimizaciones", + "remittance": "remesas", + "payments": "pagos", + "public-good": "bien público", + "quarterly": "trimestral", + "enterprise-devs": "empresas y desarrolladores" + } + }, "foot": "Orientativo - sujeto a cambios - xahau.network" }, "pt-BR": { @@ -134,22 +186,83 @@ "partnership": "Parceria", "launch": "Lançamento / bem público" }, + "tags": { + "$comment": "Card tag vocabulary. Items reference these by key via `phase` / `note`; the card renders \" · \". Edit a word here and it changes everywhere it is used. A `note` is written to agree with the `phase` it follows (gender/number), so check before reusing one under a new phase.", + "phases": { + "feature": "Funcionalidade", + "ongoing": "Contínuo", + "live": "Ao vivo", + "ga": "GA", + "pilot": "Piloto", + "launch": "Lançamento", + "partnerships": "Parcerias", + "enterprise": "Empresarial", + "event": "Evento" + }, + "notes": { + "activated": "ativada", + "ready-for-amendment": "pronta para emenda", + "in-development": "em desenvolvimento", + "alpha": "em desenvolvimento alfa", + "beta": "em desenvolvimento beta", + "audits-optimisations": "auditorias e otimizações", + "remittance": "remessas", + "payments": "pagamentos", + "public-good": "bem público", + "quarterly": "trimestral", + "enterprise-devs": "empresas e desenvolvedores" + } + }, "foot": "Direcional - sujeito a alterações - xahau.network" } }, - "items": [ + { + "id": "price-oracle", + "lane": "tech", + "status": "feature", + "quarter": "2026-Q2", + "span": 2, + "phase": "feature", + "note": "activated", + "title": { + "en": "PriceOracle", + "es": "PriceOracle", + "pt-BR": "PriceOracle" + }, + "description": { + "en": "Native on-ledger price feed primitive — tamper-resistant asset prices for DeFi, AMM and Hooks.", + "es": "Primitiva nativa de precios en el ledger — precios de activos resistentes a manipulación para DeFi, AMM y Hooks.", + "pt-BR": "Primitiva nativa de feed de preços no ledger — preços de ativos resistentes a adulterações para DeFi, AMM e Hooks." + } + }, + { + "id": "iou-reward-claim", + "lane": "tech", + "status": "feature", + "quarter": "2026-Q2", + "endQuarter": "2026-Q3", + "phase": "feature", + "note": "activated", + "title": { + "en": "IOURewardClaim", + "es": "IOURewardClaim", + "pt-BR": "IOURewardClaim" + }, + "description": { + "en": "Extends the current RewardClaim for XAH to make it available for IOU tokens as well — letting projects use Hooks to distribute rewards based on a user's token holdings.", + "es": "Extiende el RewardClaim actual de XAH para ofrecerlo también a tokens IOU — permitiendo a los proyectos usar Hooks para distribuir recompensas según las tenencias de tokens de un usuario.", + "pt-BR": "Estende o RewardClaim atual do XAH para torná-lo disponível também para tokens IOU — permitindo que projetos usem Hooks para distribuir recompensas com base nas posições de tokens de um usuário." + } + }, { "id": "rng", "lane": "tech", "status": "feature", "quarter": "2026-Q2", - "endQuarter": "2026-Q2", - "tag": { - "en": "Feature · in beta development", - "es": "Función · en desarrollo beta", - "pt-BR": "Funcionalidade · em desenvolvimento beta" - }, + "endQuarter": "2026-Q3", + "phase": "feature", + "note": "beta", "title": { "en": "Random Number Generator", "es": "Generador de números aleatorios", @@ -167,11 +280,8 @@ "status": "feature", "quarter": "2026-Q2", "endQuarter": "2026-Q3", - "tag": { - "en": "Feature · in development", - "es": "Función · en desarrollo", - "pt-BR": "Funcionalidade · em desenvolvimento" - }, + "phase": "feature", + "note": "in-development", "title": { "en": "FeatureExport", "es": "FeatureExport", @@ -189,74 +299,32 @@ "status": "feature", "quarter": "2026-Q2", "span": 2, - "tag": { - "en": "Feature · ready for amendment", - "es": "Función · lista para enmienda", - "pt-BR": "Funcionalidade · pronta para emenda" + "phase": "feature", + "note": "ready-for-amendment", + "title": { + "en": "AMM", + "es": "AMM", + "pt-BR": "AMM" }, - "title": { "en": "AMM", "es": "AMM", "pt-BR": "AMM" }, "description": { "en": "Automated Market Maker primitive for deep native liquidity on the Xahau DEX. Merged into the xahaud dev branch, awaiting amendment vote.", "es": "Primitiva Automated Market Maker para liquidez nativa profunda en el DEX de Xahau. Integrada en la rama dev de xahaud, a la espera de la votación de enmienda.", "pt-BR": "Primitiva de Automated Market Maker para liquidez nativa profunda no DEX da Xahau. Integrada ao branch dev do xahaud, aguardando votação de emenda." } }, - { - "id": "price-oracle", - "lane": "tech", - "status": "feature", - "quarter": "2026-Q2", - "span": 2, - "tag": { - "en": "Feature · ready for amendment", - "es": "Función · lista para enmienda", - "pt-BR": "Funcionalidade · pronta para emenda" - }, - "title": { - "en": "PriceOracle", - "es": "PriceOracle", - "pt-BR": "PriceOracle" - }, - "description": { - "en": "Native on-ledger price feed primitive — tamper-resistant asset prices for DeFi, AMM and Hooks. Merged into the xahaud dev branch, awaiting amendment vote.", - "es": "Primitiva nativa de precios en el ledger — precios de activos resistentes a manipulación para DeFi, AMM y Hooks. Integrada en la rama dev de xahaud, a la espera de la votación de enmienda.", - "pt-BR": "Primitiva nativa de feed de preços no ledger — preços de ativos resistentes a adulterações para DeFi, AMM e Hooks. Integrada ao branch dev do xahaud, aguardando votação de emenda." - } - }, - { - "id": "iou-reward-claim", - "lane": "tech", - "status": "feature", - "quarter": "2026-Q2", - "endQuarter": "2026-Q3", - "tag": { - "en": "Feature · in development", - "es": "Función · en desarrollo", - "pt-BR": "Funcionalidade · em desenvolvimento" - }, - "title": { - "en": "IOURewardClaim", - "es": "IOURewardClaim", - "pt-BR": "IOURewardClaim" - }, - "description": { - "en": "Extends the current RewardClaim for XAH to make it available for IOU tokens as well — letting projects use Hooks to distribute rewards based on a user's token holdings.", - "es": "Extiende el RewardClaim actual de XAH para ofrecerlo también a tokens IOU — permitiendo a los proyectos usar Hooks para distribuir recompensas según las tenencias de tokens de un usuario.", - "pt-BR": "Estende o RewardClaim atual do XAH para torná-lo disponível também para tokens IOU — permitindo que projetos usem Hooks para distribuir recompensas com base nas posições de tokens de um usuário." - } - }, { "id": "named-hook", "lane": "tech", "status": "feature", "quarter": "2026-Q3", "endQuarter": "2026-Q4", - "tag": { - "en": "Feature · in development", - "es": "Función · en desarrollo", - "pt-BR": "Funcionalidade · em desenvolvimento" + "phase": "feature", + "note": "ready-for-amendment", + "title": { + "en": "NamedHook", + "es": "NamedHook", + "pt-BR": "NamedHook" }, - "title": { "en": "NamedHook", "es": "NamedHook", "pt-BR": "NamedHook" }, "description": { "en": "Allows names to be set to identify Hooks, so different Hooks can be invoked even within the same transaction type.", "es": "Permite asignar nombres para identificar Hooks, de modo que se puedan invocar diferentes Hooks incluso dentro del mismo tipo de transacción.", @@ -269,12 +337,13 @@ "status": "feature", "quarter": "2026-Q2", "span": 3, - "tag": { - "en": "Feature · in alpha development", - "es": "Función · en desarrollo alfa", - "pt-BR": "Funcionalidade · em desenvolvimento alfa" + "phase": "feature", + "note": "alpha", + "title": { + "en": "JSHooks", + "es": "JSHooks", + "pt-BR": "JSHooks" }, - "title": { "en": "JSHooks", "es": "JSHooks", "pt-BR": "JSHooks" }, "description": { "en": "JavaScript runtime for Hooks — write Xahau smart-contract logic in JavaScript alongside the existing WebAssembly runtime, broadening the developer surface.", "es": "Entorno de ejecución JavaScript para Hooks — escribir la lógica de smart contracts en JavaScript junto al entorno WebAssembly existente, ampliando la superficie para desarrolladores.", @@ -287,11 +356,8 @@ "status": "feature", "quarter": "2026-Q2", "openEnded": true, - "tag": { - "en": "Ongoing · audits + optimisations", - "es": "Continuo · auditorías y optimizaciones", - "pt-BR": "Contínuo · auditorias e otimizações" - }, + "phase": "ongoing", + "note": "audits-optimisations", "title": { "en": "Audit & optimisations", "es": "Auditorías y optimizaciones", @@ -303,19 +369,15 @@ "pt-BR": "Auditorias de segurança e melhorias de desempenho contínuas no protocolo, Hooks e ferramentas." } }, - { "id": "coopbank-eur", "lane": "rollout", "status": "live", "quarter": "2026-Q2", - "openEnded": false, "span": 2, - "tag": { - "en": "Live · remittance", - "es": "En vivo · remesas", - "pt-BR": "Ao vivo · remessas" - }, + "openEnded": false, + "phase": "live", + "note": "remittance", "title": { "en": "COOP Bank EUR corridor scale-up to entire country.", "es": "Escalado del corredor EUR de COOP Bank a todo el país.", @@ -333,7 +395,7 @@ "status": "partnership", "quarter": "2026-Q2", "endQuarter": "2026-Q3", - "tag": { "en": "Launch", "es": "Lanzamiento", "pt-BR": "Lançamento" }, + "phase": "launch", "title": { "en": "USD stablecoin on Xahau", "es": "Stablecoin USD en Xahau", @@ -351,7 +413,7 @@ "status": "partnership", "quarter": "2026-Q4", "openEnded": true, - "tag": { "en": "Launch", "es": "Lanzamiento", "pt-BR": "Lançamento" }, + "phase": "launch", "title": { "en": "Currencies from multiple countries on Xahau", "es": "Divisas de múltiples países en Xahau", @@ -369,7 +431,7 @@ "status": "partnership", "quarter": "2026-Q3", "endQuarter": "2026-Q4", - "tag": { "en": "Launch", "es": "Lanzamiento", "pt-BR": "Lançamento" }, + "phase": "launch", "title": { "en": "GBP stablecoin on Xahau", "es": "Stablecoin GBP en Xahau", @@ -387,7 +449,7 @@ "status": "pilot", "quarter": "2026-Q3", "endQuarter": "2026-Q4", - "tag": { "en": "Pilot", "es": "Piloto", "pt-BR": "Piloto" }, + "phase": "pilot", "title": { "en": "USD corridor pilot", "es": "Piloto del corredor USD", @@ -405,7 +467,7 @@ "status": "partnership", "quarter": "2026-Q4", "openEnded": true, - "tag": { "en": "Partnerships", "es": "Alianzas", "pt-BR": "Parcerias" }, + "phase": "partnerships", "title": { "en": "Additional bank & remittance partners", "es": "Bancos y socios de remesas adicionales", @@ -423,11 +485,8 @@ "status": "live", "quarter": "2027-Q1", "span": 1, - "tag": { - "en": "GA · remittance", - "es": "GA · remesas", - "pt-BR": "GA · remessas" - }, + "phase": "ga", + "note": "remittance", "title": { "en": "Corridor expansion · GA", "es": "Expansión de corredores · GA", @@ -445,11 +504,8 @@ "status": "pilot", "quarter": "2027-Q2", "endQuarter": "2027-Q3", - "tag": { - "en": "Pilot · payments", - "es": "Piloto · pagos", - "pt-BR": "Piloto · pagamentos" - }, + "phase": "pilot", + "note": "payments", "title": { "en": "Merchant settlement pilot", "es": "Piloto de liquidación para comercios", @@ -461,18 +517,13 @@ "pt-BR": "Liquidação com stablecoins e conformidade baseada em Hooks para comerciantes." } }, - { "id": "enterprise-ga", "lane": "rollout", "status": "partnership", "quarter": "2027-Q3", "openEnded": true, - "tag": { - "en": "Enterprise", - "es": "Empresarial", - "pt-BR": "Empresarial" - }, + "phase": "enterprise", "title": { "en": "Enterprise settlement GA", "es": "GA de liquidación empresarial", @@ -484,18 +535,14 @@ "pt-BR": "Oferta de produção para parceiros regulamentados, construída sobre componentes Hooks auditados." } }, - { "id": "ngo-donations", "lane": "rollout", "status": "launch", "quarter": "2026-Q4", "openEnded": true, - "tag": { - "en": "Launch · public good", - "es": "Lanzamiento · bien público", - "pt-BR": "Lançamento · bem público" - }, + "phase": "launch", + "note": "public-good", "title": { "en": "NGO donation platform", "es": "Plataforma de donaciones para ONG", @@ -535,11 +582,8 @@ "status": "launch", "quarter": "2026-Q2", "openEnded": true, - "tag": { - "en": "Event · quarterly", - "es": "Evento · trimestral", - "pt-BR": "Evento · trimestral" - }, + "phase": "event", + "note": "quarterly", "title": { "en": "Xahau Connect", "es": "Xahau Connect", @@ -556,11 +600,8 @@ "lane": "rollout", "status": "launch", "quarter": "2026-Q4", - "tag": { - "en": "Event · enterprise + devs", - "es": "Evento · empresas y desarrolladores", - "pt-BR": "Evento · empresas e desenvolvedores" - }, + "phase": "event", + "note": "enterprise-devs", "title": { "en": "InFTF event", "es": "Evento InFTF", diff --git a/src/schemas/roadmap.ts b/src/schemas/roadmap.ts index 6e91660..c7cd735 100644 --- a/src/schemas/roadmap.ts +++ b/src/schemas/roadmap.ts @@ -29,6 +29,14 @@ export const roadmapItem = z.object({ // Ongoing activity with no firm end date. Stretches to the end of the visible // window and renders a "continues" indicator. Overrides `span`/`endQuarter`. openEnded: z.boolean().optional().default(false), + // Card tag, built from the shared vocabulary in `labels..tags`. + // `phase` is the base word ('feature' -> "Feature"), `note` the optional + // qualifier ('activated' -> "Activated"); the card renders "phase · note". + // Both are keys, not text — translations live in `labels`, not on the item. + phase: z.string().min(1).optional(), + note: z.string().min(1).optional(), + // Escape hatch for one-off tags that aren't part of the vocabulary. + // Ignored when `phase` is set. Prefer `phase`/`note` for anything reusable. tag: localized.optional(), title: localized, description: localized.optional(), @@ -59,6 +67,13 @@ export const roadmapLabels = z.object({ partnership: z.string(), launch: z.string(), }), + // Tag vocabulary — see `phase`/`note` on roadmapItem. Open records so a new + // key only has to be added to the JSON, not here; XahauRoadmap.astro fails + // the build if an item points at a key that doesn't exist. + tags: z.object({ + phases: z.record(z.string(), z.string()), + notes: z.record(z.string(), z.string()), + }), foot: z.string(), })