@voltro/cli 0.52.0 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +424 -0
- package/THIRD-PARTY-NOTICES.md +8311 -3318
- package/dist/{agentsMd-0l980yhL.js → agentsMd-BaLC10Na.js} +110 -82
- package/dist/agentsMd-DCY1RSs8.js +2 -0
- package/dist/apiBuild-CeUN55uk.js +2 -0
- package/dist/{apiBuild-CSFI8QGq.js → apiBuild-DTWp0S_q.js} +11 -5
- package/dist/bin.js +1 -1
- package/dist/build-D4ygSbnV.js +843 -0
- package/dist/{checkCommand-COmqc2cB.js → checkCommand-Dg1G7Gwd.js} +6 -6
- package/dist/{checkCommand-2SbqzukH.js → checkCommand-L7DTlpIF.js} +1 -1
- package/dist/{cloudCmd-QUXh-b5w.js → cloudCmd-DzKcSYuy.js} +1 -1
- package/dist/{codegen-VF479Cnb.js → codegen-DSLM8Su9.js} +1 -1
- package/dist/codegen-DjgxEOnD.js +2 -0
- package/dist/codegenCommand-CG_Vx4lc.js +41 -0
- package/dist/{codemodRunner-r7J9lIa7.js → codemodRunner-Cd4xkC6u.js} +109 -11
- package/dist/{commands-Cc_nV8WI.js → commands-6Kzi92Np.js} +96 -73
- package/dist/{dashboardCommand-C-gKvwqh.js → dashboardCommand-Cq1PWvI1.js} +5 -5
- package/dist/{dataCommand-BgpBHnlB.js → dataCommand-DYzW8vkv.js} +3 -3
- package/dist/{dbCommand-sHedr-NJ.js → dbCommand-B4NWZtGL.js} +278 -237
- package/dist/dbCommand-CSFWs9ev.js +2 -0
- package/dist/{dev-CRHoCEiy.js → dev-CmuvUKRq.js} +2903 -2306
- package/dist/{dev--A3nsxA3.js → dev-cKUiZZsB.js} +1 -1
- package/dist/{doctorCommand-DtfJ3FA6.js → doctorCommand-DCiFVMtZ.js} +101 -69
- package/dist/doctorCommand-J3qu4E0Y.js +2 -0
- package/dist/{dormancyCommand-Drn7o0No.js → dormancyCommand-w1TrmgYP.js} +1 -1
- package/dist/{embeddingsCommand-Z-jO1fWN.js → embeddingsCommand-CMgPyRTr.js} +1 -1
- package/dist/{envCommand-D4gCrrTZ.js → envCommand-Cyynmcfa.js} +8 -8
- package/dist/{evolveCommand-CMROeKeA.js → evolveCommand-BwvQ8dVH.js} +2 -2
- package/dist/fileConventions-l-RIXbx8.js +36 -0
- package/dist/{fileTaxonomy-DvDUV9wq.js → fileTaxonomy-CbyMQYx_.js} +42 -42
- package/dist/fontPipeline-LxIHa1vo.js +2 -0
- package/dist/fontPipeline-Tsh8kZfA.js +152 -0
- package/dist/{frameworkTableAssembly-w-XnLa3q.js → frameworkTableAssembly-D7LJuALW.js} +5 -5
- package/dist/frameworkTableAssembly-IPD1pUnZ.js +2 -0
- package/dist/imagePipeline-B_GVJgm6.js +2 -0
- package/dist/imagePipeline-CBZmjT4i.js +127 -0
- package/dist/index.js +2 -2
- package/dist/{infoCommand-DXM868o_.js → infoCommand-DlYlUPqs.js} +1 -1
- package/dist/{inspect-CtL_xTbu.js → inspect-Bd8-9wsi.js} +1 -1
- package/dist/inspect-CuoDInfZ.js +2 -0
- package/dist/{inspectGateHint-BjnFubmH.js → inspectGateHint-4LxkNtrz.js} +1 -1
- package/dist/manifestBuild-C4-J1-m_.js +2 -0
- package/dist/{manifestBuild-CuU1VrSm.js → manifestBuild-Cqgsx2bM.js} +1 -1
- package/dist/{metaCommands-C6RFmF1r.js → metaCommands-x7RCi2AF.js} +9 -3
- package/dist/{migrate-D0F-eTlK.js → migrate-BK_Bbx-_.js} +2 -2
- package/dist/mobileCommand-D9O6iq3D.js +428 -0
- package/dist/mobileCommand-DAum7tsG.js +2 -0
- package/dist/{pageConvention-CzUiSbtU.js → pageConvention-CMpfDN6r.js} +1 -1
- package/dist/{privacyCommand-DGdopOI6.js → privacyCommand-BCa2OoZG.js} +1 -1
- package/dist/{probeCommand-C9gazU0H.js → probeCommand-_C0YU207.js} +83 -24
- package/dist/{projectScaffold-EzlErR4E.js → projectScaffold-CJfP-xbT.js} +1 -1
- package/dist/{projectScaffold-B4dmTlwT.js → projectScaffold-CSN0OzBV.js} +2 -2
- package/dist/renderModeScan-43yQ2opo.js +147 -0
- package/dist/{renderProfile-Ck32Fzxr.js → renderProfile-DvrhVJHa.js} +2 -2
- package/dist/{runtimeTrace-BPQyCmC5.js → runtimeTrace-CGWx1Q6l.js} +1 -1
- package/dist/{sdkgen-Se88ifTd.js → sdkgen-CDGHQUFj.js} +1 -1
- package/dist/serveCommand-BiPe8BJm.js +2 -0
- package/dist/{serveCommand-DkP3OT0W.js → serveCommand-Bje09q1v.js} +889 -825
- package/dist/serveEntry.js +1 -1
- package/dist/start-B0bnJgxI.js +3 -0
- package/dist/{start-jw89Xbqy.js → start-Clz-1BHB.js} +633 -455
- package/dist/startEntry.js +1 -1
- package/dist/{staticCommand-BwNEDlSU.js → staticCommand-ey0kYmOT.js} +1 -1
- package/dist/{subcommandNames-DpYs3DXr.js → subcommandNames-CDzfEtKV.js} +3 -3
- package/dist/{templates-BR-fb4SP.js → templates-BTWZkJJT.js} +41 -9
- package/dist/{test-f3amja6a.js → test-D_kW4KMj.js} +1 -1
- package/dist/updateCommand-CIoVDKnj.js +2 -0
- package/dist/{updateCommand-BMk2e4ky.js → updateCommand-CRJlAOaM.js} +139 -115
- package/dist/{webDev-BgWL9gKV.js → webDev-DSI9SOhs.js} +1598 -1028
- package/dist/webDev-DlvZO30c.js +2 -0
- package/dist/{webhooksCommand-CoIO3jbj.js → webhooksCommand-BvzXNHji.js} +1 -1
- package/dist/workspaceDeps-RKEkX92S.js +45 -0
- package/package.json +60 -19
- package/templates/AGENTS.core.md +2 -0
- package/templates/AGENTS.md +8 -4
- package/templates/agent-docs/_index.md +6 -4
- package/templates/agent-docs/_manifest.json +21 -5
- package/templates/agent-docs/ai.md +6 -6
- package/templates/agent-docs/authentication.md +73 -1
- package/templates/agent-docs/cli.md +6 -3
- package/templates/agent-docs/configuration.md +17 -0
- package/templates/agent-docs/data.md +522 -29
- package/templates/agent-docs/database/advancedqueries.md +8 -8
- package/templates/agent-docs/database/columntypes.md +2 -2
- package/templates/agent-docs/database/migrations.md +1 -1
- package/templates/agent-docs/database/querying.md +1 -1
- package/templates/agent-docs/database/schema.md +1 -1
- package/templates/agent-docs/database/seedsdialects.md +65 -3
- package/templates/agent-docs/database/transactions.md +3 -3
- package/templates/agent-docs/deployment.md +8 -0
- package/templates/agent-docs/internationalization.md +4 -2
- package/templates/agent-docs/introduction.md +32 -1
- package/templates/agent-docs/local-first-mobile.md +226 -30
- package/templates/agent-docs/observability.md +5 -1
- package/templates/agent-docs/plugins/atlassian.md +2 -2
- package/templates/agent-docs/plugins/audit.md +2 -2
- package/templates/agent-docs/plugins/auth.md +1 -1
- package/templates/agent-docs/plugins/billing.md +1 -1
- package/templates/agent-docs/plugins/cdc-out.md +8 -3
- package/templates/agent-docs/plugins/comments.md +164 -0
- package/templates/agent-docs/plugins/notifications.md +47 -4
- package/templates/agent-docs/plugins/presence.md +45 -3
- package/templates/agent-docs/plugins/prometheus.md +3 -1
- package/templates/agent-docs/plugins/queue.md +172 -0
- package/templates/agent-docs/plugins.md +17 -13
- package/templates/agent-docs/reference.md +35 -4
- package/templates/agent-docs/routing.md +585 -7
- package/templates/agent-docs/scheduling.md +1 -1
- package/templates/agent-docs/schema-driven-ui.md +226 -4
- package/templates/agent-docs/security.md +3 -3
- package/templates/agent-docs/templates/appshells.md +36 -4
- package/templates/agent-docs/whats-new.md +134 -66
- package/templates/apps/api-ai/package.json +6 -6
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-sqlite/package.json +8 -8
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/package.json +9 -9
- package/templates/apps/api-collab/README.md +3 -3
- package/templates/apps/api-collab/app.config.ts +1 -1
- package/templates/apps/api-collab/database/schema.ts +12 -8
- package/templates/apps/api-collab/mutations/documents.create.mutation.server.ts +1 -1
- package/templates/apps/api-collab/mutations/documents.setBody.mutation.server.ts +1 -1
- package/templates/apps/api-collab/mutations/documents.setBody.mutation.ts +1 -1
- package/templates/apps/api-collab/package.json +8 -8
- package/templates/apps/api-collab/template.json +1 -1
- package/templates/apps/api-collab/tests/documents.setBody.test.ts +10 -2
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-row-history/package.json +8 -8
- package/templates/apps/api-saas/package.json +11 -10
- package/templates/apps/api-saas-starter/package.json +10 -10
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/app.config.ts +26 -2
- package/templates/apps/changelog/content/releases/{0.1.0.mdx → v0-1-0.mdx} +0 -1
- package/templates/apps/changelog/content/releases/{0.2.0.mdx → v0-2-0.mdx} +0 -1
- package/templates/apps/changelog/package.json +9 -8
- package/templates/apps/changelog/src/collections/releases.collection.ts +48 -0
- package/templates/apps/changelog/src/globals.d.ts +1 -1
- package/templates/apps/changelog/src/locales/de.ts +1 -1
- package/templates/apps/changelog/src/locales/en.ts +1 -1
- package/templates/apps/changelog/src/pages/[locale]/[slug]/page.tsx +7 -4
- package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +13 -41
- package/templates/apps/changelog/src/pages/[slug]/page.test.tsx +26 -51
- package/templates/apps/changelog/src/pages/[slug]/page.tsx +21 -19
- package/templates/apps/changelog/src/pages/page.test.tsx +14 -37
- package/templates/apps/changelog/src/pages/page.tsx +18 -12
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-app/package.json +9 -9
- package/templates/apps/frontend-auth/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-cms/package.json +9 -9
- package/templates/apps/frontend-collab/README.md +43 -24
- package/templates/apps/frontend-collab/app.config.ts +3 -3
- package/templates/apps/frontend-collab/package.json +14 -10
- package/templates/apps/frontend-collab/src/locales/de.ts +1 -2
- package/templates/apps/frontend-collab/src/locales/en.ts +1 -2
- package/templates/apps/frontend-collab/src/pages/page.test.tsx +72 -76
- package/templates/apps/frontend-collab/src/pages/page.tsx +45 -22
- package/templates/apps/frontend-collab/template.json +2 -2
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-docs/content/docs/de/guides/first-page.md +4 -0
- package/templates/apps/frontend-docs/content/docs/de/intro/getting-started.md +4 -0
- package/templates/apps/frontend-docs/content/docs/en/guides/first-page.md +4 -0
- package/templates/apps/frontend-docs/content/docs/en/intro/getting-started.md +4 -0
- package/templates/apps/frontend-docs/package.json +9 -6
- package/templates/apps/frontend-docs/src/collections/docs.collection.ts +21 -0
- package/templates/apps/frontend-docs/src/locales/de.ts +0 -5
- package/templates/apps/frontend-docs/src/locales/en.ts +0 -5
- package/templates/apps/frontend-docs/src/pages/[locale]/docs/[...slug]/page.tsx +17 -9
- package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +10 -5
- package/templates/apps/frontend-docs/src/pages/[locale]/page.tsx +8 -0
- package/templates/apps/frontend-docs/src/pages/docs/[...slug]/page.test.tsx +53 -15
- package/templates/apps/frontend-docs/src/pages/docs/[...slug]/page.tsx +27 -33
- package/templates/apps/frontend-docs/src/pages/page.test.tsx +17 -3
- package/templates/apps/frontend-docs/src/pages/page.tsx +16 -12
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/README.md +48 -0
- package/templates/apps/frontend-landing/app.config.ts +28 -0
- package/templates/apps/frontend-landing/package.json +7 -6
- package/templates/apps/frontend-landing/src/assets/hero.jpg +0 -0
- package/templates/apps/frontend-landing/src/fonts/Geist-Variable.woff2 +0 -0
- package/templates/apps/frontend-landing/src/fonts/LICENSE-Geist.txt +92 -0
- package/templates/apps/frontend-landing/src/globals.css +15 -0
- package/templates/apps/frontend-landing/src/globals.d.ts +17 -0
- package/templates/apps/frontend-landing/src/locales/de.ts +3 -2
- package/templates/apps/frontend-landing/src/locales/en.ts +5 -2
- package/templates/apps/frontend-landing/src/pages/page.test.tsx +79 -0
- package/templates/apps/frontend-landing/src/pages/page.tsx +26 -3
- package/templates/apps/frontend-landing/template.json +2 -2
- package/templates/apps/frontend-portal/package.json +8 -8
- package/templates/apps/frontend-saas/package.json +8 -8
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/content/posts/cms-to-ssg.md +12 -0
- package/templates/apps/frontend-static-blog/content/posts/hello-static.md +12 -0
- package/templates/apps/frontend-static-blog/content/posts/islands-not-hydration.md +14 -0
- package/templates/apps/frontend-static-blog/package.json +9 -6
- package/templates/apps/frontend-static-blog/src/collections/posts.collection.ts +27 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/[slug]/page.tsx +7 -4
- package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +7 -1
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug]/page.test.tsx +19 -9
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug]/page.tsx +19 -21
- package/templates/apps/frontend-static-blog/src/pages/page.test.tsx +18 -5
- package/templates/apps/frontend-static-blog/src/pages/page.tsx +21 -14
- package/templates/apps/frontend-status/package.json +8 -8
- package/templates/apps/mobile-app/package.json +12 -11
- package/templates/apps/mobile-app/src/lib/deeplinks.ts +29 -17
- package/templates/apps/mobile-app/tests/deeplinks.test.ts +16 -0
- package/dist/agentsMd-SDDSkyl4.js +0 -2
- package/dist/apiBuild-BYBpL7Pz.js +0 -2
- package/dist/build-CPgcMQug.js +0 -793
- package/dist/codegen-CctkDO-1.js +0 -2
- package/dist/codegenCommand-DCdG2JN-.js +0 -137
- package/dist/dbCommand-DNb6yeOG.js +0 -2
- package/dist/doctorCommand-CqoWA2p5.js +0 -2
- package/dist/fileConventions-DOqD3lPS.js +0 -34
- package/dist/frameworkTableAssembly-C6ETawPR.js +0 -2
- package/dist/inspect-CuGDYES0.js +0 -2
- package/dist/manifestBuild-CPjhvM62.js +0 -2
- package/dist/renderModeScan-CcH2X1_D.js +0 -120
- package/dist/serveCommand-DLc-BznW.js +0 -2
- package/dist/start-DfL3fOiN.js +0 -3
- package/dist/updateCommand-5gFVfK5q.js +0 -2
- package/dist/webDev-CZbTsDcH.js +0 -2
- package/templates/apps/changelog/scripts/generate-rss.mjs +0 -38
- package/templates/apps/changelog/src/lib/releases.ts +0 -21
- package/templates/apps/frontend-static-blog/src/content/posts.ts +0 -64
|
@@ -1,49 +1,25 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
//
|
|
3
|
-
// `/` changelog index — lists every release, newest first
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// `/` changelog index — lists every release, newest first, loaded through
|
|
4
|
+
// the REAL `releases` content collection (this app's content/releases/*.mdx)
|
|
5
|
+
// so the test exercises frontmatter decoding, draft/future filtering and
|
|
6
|
+
// ordering — no canned copies to drift.
|
|
7
7
|
|
|
8
8
|
import { afterEach, describe, expect, test, vi } from 'vitest'
|
|
9
9
|
import { act, createElement, type ReactNode } from 'react'
|
|
10
10
|
import { createRoot, type Root } from 'react-dom/client'
|
|
11
|
+
import { dirname, join } from 'node:path'
|
|
12
|
+
import { fileURLToPath } from 'node:url'
|
|
13
|
+
import { setContentRoot } from '@voltro/content'
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
version: string
|
|
14
|
-
releasedAt: string
|
|
15
|
-
slug: string
|
|
16
|
-
tags: ReadonlyArray<string>
|
|
17
|
-
title: string
|
|
18
|
-
summary: string
|
|
19
|
-
html: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const releases: ReadonlyArray<Release> = [
|
|
23
|
-
{
|
|
24
|
-
version: '0.2.0',
|
|
25
|
-
releasedAt: '2026-06-21',
|
|
26
|
-
slug: 'v0-2-0',
|
|
27
|
-
tags: ['feature', 'performance'],
|
|
28
|
-
title: 'Typed env + faster boot',
|
|
29
|
-
summary: 'Declare your environment once.',
|
|
30
|
-
html: '<p>body</p>',
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
version: '0.1.0',
|
|
34
|
-
releasedAt: '2026-01-15',
|
|
35
|
-
slug: 'v0-1-0',
|
|
36
|
-
tags: ['feature'],
|
|
37
|
-
title: 'First release',
|
|
38
|
-
summary: 'The initial cut.',
|
|
39
|
-
html: '<p>body</p>',
|
|
40
|
-
},
|
|
41
|
-
]
|
|
15
|
+
setContentRoot(join(dirname(fileURLToPath(import.meta.url)), '..', '..'))
|
|
42
16
|
|
|
43
|
-
vi.mock('../lib/releases', () => ({ releases }))
|
|
44
17
|
// `withLocalePrefix` (lib/locale) reads useLocation; the page pins the locale
|
|
45
18
|
// via useLocale, so any bare path is fine here.
|
|
46
|
-
vi.mock('@voltro/web', () => ({
|
|
19
|
+
vi.mock('@voltro/web', () => ({
|
|
20
|
+
useLocation: () => '/',
|
|
21
|
+
useLoaderData: () => entries,
|
|
22
|
+
}))
|
|
47
23
|
// Chrome (meta via getCatalog + the empty state) comes from @voltro/i18n;
|
|
48
24
|
// stub it (identity catalog defs, useLocale → en, <T> passes children through).
|
|
49
25
|
vi.mock('@voltro/i18n', () => ({
|
|
@@ -57,7 +33,8 @@ vi.mock('@voltro/i18n', () => ({
|
|
|
57
33
|
T: ({ id }: { id: string }) => id,
|
|
58
34
|
}))
|
|
59
35
|
|
|
60
|
-
const { default: ChangelogIndex, meta, renderMode, interactive } = await import('./page')
|
|
36
|
+
const { default: ChangelogIndex, meta, renderMode, interactive, loader } = await import('./page')
|
|
37
|
+
const entries = await loader()
|
|
61
38
|
|
|
62
39
|
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
63
40
|
|
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
// locale) and mirrored at `/de` by src/pages/[locale]/index.tsx. Pure static
|
|
3
3
|
// render with no client JS (`interactive: 'none'`).
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
5
|
+
// The list comes from the `releases` CONTENT COLLECTION: the loader runs at
|
|
6
|
+
// build time, decodes each entry's frontmatter against the schema, and
|
|
7
|
+
// filters drafts + future-dated entries (visibleReleases). The release list
|
|
8
|
+
// itself is CONTENT and is NOT translated — only the chrome comes from the
|
|
9
|
+
// catalogs. Entry links are locale-prefixed via `withLocalePrefix`.
|
|
10
10
|
|
|
11
11
|
import type { ReactNode } from 'react'
|
|
12
12
|
import type { PageMeta } from '@voltro/web'
|
|
13
|
+
import { useLoaderData } from '@voltro/web'
|
|
14
|
+
import { getCollection } from '@voltro/content'
|
|
13
15
|
import { T, useLocale } from '@voltro/i18n'
|
|
14
|
-
import { releases } from '../
|
|
16
|
+
import { releases, tagsOf, visibleReleases, type ReleaseData, type ReleaseEntry } from '../collections/releases.collection'
|
|
15
17
|
import { getCatalog, withLocalePrefix } from '../lib/locale'
|
|
16
18
|
|
|
17
19
|
export const renderMode = 'static' as const
|
|
@@ -25,9 +27,13 @@ export const meta = ({ locale }: { locale: string }): PageMeta => {
|
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
export const loader = async (): Promise<ReadonlyArray<ReleaseEntry>> =>
|
|
31
|
+
visibleReleases(await getCollection<ReleaseData>(releases.name))
|
|
32
|
+
|
|
28
33
|
export default function ChangelogIndex(): ReactNode {
|
|
34
|
+
const entries = useLoaderData<ReadonlyArray<ReleaseEntry>>()
|
|
29
35
|
const locale = useLocale()
|
|
30
|
-
if (
|
|
36
|
+
if (entries.length === 0) {
|
|
31
37
|
return (
|
|
32
38
|
<p>
|
|
33
39
|
<T id="empty.body" values={{ code: (chunks: ReactNode) => <code>{chunks}</code> }} />
|
|
@@ -36,18 +42,18 @@ export default function ChangelogIndex(): ReactNode {
|
|
|
36
42
|
}
|
|
37
43
|
return (
|
|
38
44
|
<div>
|
|
39
|
-
{
|
|
45
|
+
{entries.map((release) => (
|
|
40
46
|
<article key={release.slug} className="changelog-entry">
|
|
41
47
|
<p className="changelog-meta">
|
|
42
|
-
<time dateTime={release.releasedAt}>{release.releasedAt}</time> · v{release.version}
|
|
43
|
-
{release.
|
|
48
|
+
<time dateTime={release.data.releasedAt}>{release.data.releasedAt}</time> · v{release.data.version}
|
|
49
|
+
{tagsOf(release.data).map((tag) => (
|
|
44
50
|
<span key={tag} className="changelog-tag">{tag}</span>
|
|
45
51
|
))}
|
|
46
52
|
</p>
|
|
47
53
|
<h2>
|
|
48
|
-
<a href={withLocalePrefix(`/${release.slug}`, locale)}>{release.title}</a>
|
|
54
|
+
<a href={withLocalePrefix(`/${release.slug}`, locale)}>{release.data.title}</a>
|
|
49
55
|
</h2>
|
|
50
|
-
<p>{release.summary}</p>
|
|
56
|
+
<p>{release.data.summary}</p>
|
|
51
57
|
</article>
|
|
52
58
|
))}
|
|
53
59
|
</div>
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
"test": "voltro test"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@{{projectName}}/api": "0.
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/client": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/i18n": "0.
|
|
20
|
-
"@voltro/ui-shadcn": "0.
|
|
21
|
-
"@voltro/web": "0.
|
|
15
|
+
"@{{projectName}}/api": "0.54.0",
|
|
16
|
+
"@voltro/cli": "0.54.0",
|
|
17
|
+
"@voltro/client": "0.54.0",
|
|
18
|
+
"@voltro/env": "0.54.0",
|
|
19
|
+
"@voltro/i18n": "0.54.0",
|
|
20
|
+
"@voltro/ui-shadcn": "0.54.0",
|
|
21
|
+
"@voltro/web": "0.54.0",
|
|
22
22
|
"react": "^19.0.0",
|
|
23
23
|
"react-dom": "^19.0.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/devtools": "0.
|
|
26
|
+
"@voltro/devtools": "0.54.0",
|
|
27
27
|
"@types/react": "^19.0.0",
|
|
28
28
|
"@types/react-dom": "^19.0.0",
|
|
29
29
|
"jsdom": "^30.0.0",
|
|
@@ -12,19 +12,19 @@
|
|
|
12
12
|
"typecheck": "tsc --noEmit"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@{{projectName}}/api": "0.
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/client": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/i18n": "0.
|
|
20
|
-
"@voltro/ui-shadcn": "0.
|
|
21
|
-
"@voltro/web": "0.
|
|
15
|
+
"@{{projectName}}/api": "0.54.0",
|
|
16
|
+
"@voltro/cli": "0.54.0",
|
|
17
|
+
"@voltro/client": "0.54.0",
|
|
18
|
+
"@voltro/env": "0.54.0",
|
|
19
|
+
"@voltro/i18n": "0.54.0",
|
|
20
|
+
"@voltro/ui-shadcn": "0.54.0",
|
|
21
|
+
"@voltro/web": "0.54.0",
|
|
22
22
|
"react": "^19.0.0",
|
|
23
23
|
"react-dom": "^19.0.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/devtools": "0.
|
|
27
|
-
"@voltro/testing": "0.
|
|
26
|
+
"@voltro/devtools": "0.54.0",
|
|
27
|
+
"@voltro/testing": "0.54.0",
|
|
28
28
|
"@types/react": "^19.0.0",
|
|
29
29
|
"@types/react-dom": "^19.0.0",
|
|
30
30
|
"jsdom": "^30.0.0",
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
"test": "voltro test"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@{{projectName}}/api": "0.
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/client": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/i18n": "0.
|
|
20
|
-
"@voltro/ui-shadcn": "0.
|
|
21
|
-
"@voltro/web": "0.
|
|
15
|
+
"@{{projectName}}/api": "0.54.0",
|
|
16
|
+
"@voltro/cli": "0.54.0",
|
|
17
|
+
"@voltro/client": "0.54.0",
|
|
18
|
+
"@voltro/env": "0.54.0",
|
|
19
|
+
"@voltro/i18n": "0.54.0",
|
|
20
|
+
"@voltro/ui-shadcn": "0.54.0",
|
|
21
|
+
"@voltro/web": "0.54.0",
|
|
22
22
|
"react": "^19.0.0",
|
|
23
23
|
"react-dom": "^19.0.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/devtools": "0.
|
|
26
|
+
"@voltro/devtools": "0.54.0",
|
|
27
27
|
"@types/react": "^19.0.0",
|
|
28
28
|
"@types/react-dom": "^19.0.0",
|
|
29
29
|
"jsdom": "^30.0.0",
|
|
@@ -12,17 +12,17 @@
|
|
|
12
12
|
"typecheck": "tsc --noEmit"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/client": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/i18n": "0.
|
|
19
|
-
"@voltro/ui-shadcn": "0.
|
|
20
|
-
"@voltro/web": "0.
|
|
15
|
+
"@voltro/cli": "0.54.0",
|
|
16
|
+
"@voltro/client": "0.54.0",
|
|
17
|
+
"@voltro/env": "0.54.0",
|
|
18
|
+
"@voltro/i18n": "0.54.0",
|
|
19
|
+
"@voltro/ui-shadcn": "0.54.0",
|
|
20
|
+
"@voltro/web": "0.54.0",
|
|
21
21
|
"react": "^19.0.0",
|
|
22
22
|
"react-dom": "^19.0.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@voltro/devtools": "0.
|
|
25
|
+
"@voltro/devtools": "0.54.0",
|
|
26
26
|
"@types/react": "^19.0.0",
|
|
27
27
|
"@types/react-dom": "^19.0.0",
|
|
28
28
|
"jsdom": "^30.0.0",
|
|
@@ -12,19 +12,19 @@
|
|
|
12
12
|
"test": "voltro test"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@{{projectName}}/api": "0.
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/client": "0.
|
|
18
|
-
"@voltro/cms": "0.
|
|
19
|
-
"@voltro/env": "0.
|
|
20
|
-
"@voltro/i18n": "0.
|
|
21
|
-
"@voltro/ui-shadcn": "0.
|
|
22
|
-
"@voltro/web": "0.
|
|
15
|
+
"@{{projectName}}/api": "0.54.0",
|
|
16
|
+
"@voltro/cli": "0.54.0",
|
|
17
|
+
"@voltro/client": "0.54.0",
|
|
18
|
+
"@voltro/cms": "0.54.0",
|
|
19
|
+
"@voltro/env": "0.54.0",
|
|
20
|
+
"@voltro/i18n": "0.54.0",
|
|
21
|
+
"@voltro/ui-shadcn": "0.54.0",
|
|
22
|
+
"@voltro/web": "0.54.0",
|
|
23
23
|
"react": "^19.0.0",
|
|
24
24
|
"react-dom": "^19.0.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@voltro/devtools": "0.
|
|
27
|
+
"@voltro/devtools": "0.54.0",
|
|
28
28
|
"@types/react": "^19.0.0",
|
|
29
29
|
"@types/react-dom": "^19.0.0",
|
|
30
30
|
"jsdom": "^30.0.0",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Voltro collaborative-editing frontend (template: **frontend-collab**).
|
|
4
4
|
|
|
5
|
-
A document editor
|
|
5
|
+
A **rich-text** document editor — Tiptap, bound to a `crdtDoc()` body over the
|
|
6
6
|
framework's reactive loop. Type in **two browser tabs at once** and the edits
|
|
7
7
|
**converge** — no last-write-wins loser — because the api merges every update
|
|
8
8
|
into the shared document server-side, then broadcasts the merged result back
|
|
@@ -30,45 +30,64 @@ same URL and type in both. Every keystroke merges — both tabs' edits survive.
|
|
|
30
30
|
```
|
|
31
31
|
Tab A api (api-collab)
|
|
32
32
|
───── ────────────────
|
|
33
|
-
|
|
34
|
-
│
|
|
35
|
-
│ →
|
|
33
|
+
useCrdtDoc → CrdtDocHandle documents.setBody (crdtDoc 'body' column)
|
|
34
|
+
│ editor edit → doc.onUpdate └─ runtime folds the update into the
|
|
35
|
+
│ (local:true) → push ──rpc──► STORED state (mergeCrdtStates) → merged
|
|
36
36
|
▼ └─► reactive delta ──WS──► every tab
|
|
37
37
|
useSubscription('app',
|
|
38
|
-
'documents.list') ◄──WS── merged body ──
|
|
38
|
+
'documents.list') ◄──WS── merged body ── folded (local:false → no push back)
|
|
39
|
+
│
|
|
40
|
+
└─ useCrdtEditor({ doc }) → Tiptap <EditorContent>
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
1. **`useSubscription('app', 'documents.list')`** streams the shared document,
|
|
42
|
-
including its **merged** `body` (encoded CRDT bytes), on every write.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
including its **merged** `body` (encoded CRDT bytes), on every write. That
|
|
45
|
+
value is `useCrdtDoc`'s `remote` — `null` means *not loaded*, not *empty*.
|
|
46
|
+
2. **`useCrdtDoc`** owns one sync client and one live CRDT document for the
|
|
47
|
+
cell. **`useCrdtEditor({ doc })`** mounts a Tiptap editor over it, so typing
|
|
48
|
+
mutates the shared document directly.
|
|
49
|
+
3. Those edits surface as `doc.onUpdate(update, { local: true })` and are queued
|
|
50
|
+
as **incremental** updates through **`useMutation('app',
|
|
51
|
+
'documents.setBody')`** — a keystroke is bytes, not the whole document.
|
|
52
|
+
4. The api's **authoritative server-side merge** folds each update into the
|
|
47
53
|
stored state before broadcasting — so concurrent edits converge.
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
5. Incoming bodies are **folded back** into the same document. Those folds
|
|
55
|
+
report `local: false`, and the hook's **echo guard** drops them: without it,
|
|
56
|
+
one keystroke becomes one server write *per open tab*.
|
|
50
57
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
The two names the framework cannot derive are the app's to supply, and they are
|
|
59
|
+
the whole binding: `push` = the `setBody` mutation, `remote` = the `documents`
|
|
60
|
+
subscription. The sync-client lifecycle, offline queue, bounded retry,
|
|
61
|
+
`outstanding`/`synced` and the echo guard all live in the hook.
|
|
54
62
|
|
|
55
|
-
##
|
|
63
|
+
## `renderMode = 'client'`
|
|
56
64
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
`page.tsx` sets it explicitly. A rich-text editor needs a browser: `renderMode`
|
|
66
|
+
defaults to `'static'`, which pre-renders at build time, and Tiptap's
|
|
67
|
+
`new Editor()` finds no `window` there — the build fails. There is nothing
|
|
68
|
+
worth pre-rendering for a document that arrives over a live subscription
|
|
69
|
+
anyway.
|
|
70
|
+
|
|
71
|
+
## Not wired here: presence carets
|
|
72
|
+
|
|
73
|
+
Live "who else is editing" carets need two things this template does not ship:
|
|
74
|
+
a `PresenceChannel` bound to the app's broadcast broker, and an
|
|
75
|
+
`AwarenessTransport` over a `delivery: 'latest'` event
|
|
76
|
+
(`useCrdtEditor({ doc, awareness, user })`). Both are runtime bindings, not
|
|
77
|
+
zero-infra in a single dev process across tabs. The convergence above needs
|
|
78
|
+
none of it — it rides the reactive engine you already have.
|
|
62
79
|
|
|
63
80
|
## Files
|
|
64
81
|
|
|
65
82
|
```
|
|
66
83
|
app.config.ts type:web + the `apis: { app }` wiring
|
|
67
|
-
package.json depends on @{{projectName}}/api (the sibling)
|
|
84
|
+
package.json depends on @{{projectName}}/api (the sibling),
|
|
85
|
+
@voltro/local-first + the four @tiptap/* peers
|
|
68
86
|
src/pages/
|
|
69
87
|
layout.tsx root shell (header + <main>)
|
|
70
|
-
page.tsx the collaborative editor — subscription +
|
|
71
|
-
page.test.tsx
|
|
88
|
+
page.tsx the collaborative editor — subscription + useCrdtDoc + useCrdtEditor
|
|
89
|
+
page.test.tsx page-contract tests (jsdom) — states, create, and a
|
|
90
|
+
server-side fold converging into the open editor
|
|
72
91
|
src/locales/{en,de}.ts bilingual UI strings
|
|
73
92
|
```
|
|
74
93
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Web app for the {{projectName}} project. Read by `voltro dev` / `voltro start`.
|
|
2
2
|
//
|
|
3
|
-
// This is the COLLABORATIVE-EDITING frontend — a
|
|
4
|
-
// `
|
|
5
|
-
// browser tabs and type in both: the edits CONVERGE (no last-write-wins loser)
|
|
3
|
+
// This is the COLLABORATIVE-EDITING frontend — a Tiptap rich-text editor bound
|
|
4
|
+
// to a `crdtDoc()` document body over the framework's reactive loop. Open it in
|
|
5
|
+
// two browser tabs and type in both: the edits CONVERGE (no last-write-wins loser)
|
|
6
6
|
// because the api's server-authoritative CRDT merge folds every update into the
|
|
7
7
|
// stored state, then the reactive engine broadcasts the merged row back. Zero
|
|
8
8
|
// infra — the merge + broadcast are in-process on a single `voltro dev`.
|
|
@@ -12,20 +12,24 @@
|
|
|
12
12
|
"typecheck": "tsc --noEmit"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@{{projectName}}/api": "0.
|
|
16
|
-
"@
|
|
17
|
-
"@
|
|
18
|
-
"@
|
|
19
|
-
"@
|
|
20
|
-
"@voltro/
|
|
21
|
-
"@voltro/
|
|
22
|
-
"@voltro/
|
|
15
|
+
"@{{projectName}}/api": "0.54.0",
|
|
16
|
+
"@tiptap/extension-collaboration": "^3.0.0",
|
|
17
|
+
"@tiptap/extension-collaboration-caret": "^3.0.0",
|
|
18
|
+
"@tiptap/react": "^3.0.0",
|
|
19
|
+
"@tiptap/starter-kit": "^3.0.0",
|
|
20
|
+
"@voltro/cli": "0.54.0",
|
|
21
|
+
"@voltro/client": "0.54.0",
|
|
22
|
+
"@voltro/env": "0.54.0",
|
|
23
|
+
"@voltro/i18n": "0.54.0",
|
|
24
|
+
"@voltro/local-first": "0.54.0",
|
|
25
|
+
"@voltro/ui-shadcn": "0.54.0",
|
|
26
|
+
"@voltro/web": "0.54.0",
|
|
23
27
|
"react": "^19.0.0",
|
|
24
28
|
"react-dom": "^19.0.0"
|
|
25
29
|
},
|
|
26
30
|
"devDependencies": {
|
|
27
|
-
"@voltro/devtools": "0.
|
|
28
|
-
"@voltro/testing": "0.
|
|
31
|
+
"@voltro/devtools": "0.54.0",
|
|
32
|
+
"@voltro/testing": "0.54.0",
|
|
29
33
|
"@types/react": "^19.0.0",
|
|
30
34
|
"@types/react-dom": "^19.0.0",
|
|
31
35
|
"jsdom": "^30.0.0",
|
|
@@ -14,7 +14,7 @@ export default defineLocale<typeof en>()({
|
|
|
14
14
|
'lang.label': 'Sprache',
|
|
15
15
|
|
|
16
16
|
'editor.heading': 'Kollaborativer Editor',
|
|
17
|
-
'editor.intro': 'Öffne diese Seite in <strong>zwei Browser-Tabs</strong> und tippe in beiden. Die Änderungen <strong>konvergieren</strong> — kein Last-Write-Wins-Verlierer — weil die API jedes Update serverseitig in den gemeinsamen <code>
|
|
17
|
+
'editor.intro': 'Öffne diese Seite in <strong>zwei Browser-Tabs</strong> und tippe in beiden. Die Änderungen <strong>konvergieren</strong> — kein Last-Write-Wins-Verlierer — weil die API jedes Update serverseitig in den gemeinsamen <code>crdtDoc()</code>-Body mergt und live zurücksendet.',
|
|
18
18
|
'editor.status.tenant': 'Mandant <code>{tenant}</code> · Revision {revision}',
|
|
19
19
|
'editor.status.saving': ' · synchronisiere…',
|
|
20
20
|
'editor.status.loading': ' · lädt…',
|
|
@@ -24,6 +24,5 @@ export default defineLocale<typeof en>()({
|
|
|
24
24
|
'editor.create': 'Dokument erstellen',
|
|
25
25
|
'editor.creating': 'Wird erstellt…',
|
|
26
26
|
'editor.docLabel': 'Bearbeite <strong>{title}</strong>',
|
|
27
|
-
'editor.placeholder': 'Fang an zu tippen — jeder Tastendruck synchronisiert und mergt.',
|
|
28
27
|
'editor.hint': 'Tipp: Öffne einen zweiten Tab und tippe gleichzeitig. Beide Änderungen bleiben erhalten.',
|
|
29
28
|
})
|
|
@@ -16,7 +16,7 @@ export default defineCatalog({
|
|
|
16
16
|
|
|
17
17
|
// ---- Editor page (pages/page.tsx) ----
|
|
18
18
|
'editor.heading': 'Collaborative editor',
|
|
19
|
-
'editor.intro': 'Open this page in <strong>two browser tabs</strong> and type in both. Edits <strong>converge</strong> — no last-write-wins loser — because the api merges every update into the shared <code>
|
|
19
|
+
'editor.intro': 'Open this page in <strong>two browser tabs</strong> and type in both. Edits <strong>converge</strong> — no last-write-wins loser — because the api merges every update into the shared <code>crdtDoc()</code> body server-side, then broadcasts it back live.',
|
|
20
20
|
'editor.status.tenant': 'Tenant <code>{tenant}</code> · revision {revision}',
|
|
21
21
|
'editor.status.saving': ' · syncing…',
|
|
22
22
|
'editor.status.loading': ' · loading…',
|
|
@@ -26,6 +26,5 @@ export default defineCatalog({
|
|
|
26
26
|
'editor.create': 'Create document',
|
|
27
27
|
'editor.creating': 'Creating…',
|
|
28
28
|
'editor.docLabel': 'Editing <strong>{title}</strong>',
|
|
29
|
-
'editor.placeholder': 'Start typing — every keystroke syncs and merges.',
|
|
30
29
|
'editor.hint': 'Tip: open a second tab and type at the same time. Both edits survive.',
|
|
31
30
|
} as const)
|