create-grist-widget 0.2.13 → 0.2.16
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/package.json +1 -1
- package/template/.github/workflows/deploy.yml +6 -2
- package/template/README.md +14 -1
- package/template/package.json +1 -1
- package/template/scripts/deploy.mjs +80 -9
- package/template/src/components/channel-notice.tsx +2 -2
- package/template/src/components/grist-status-chip.tsx +18 -19
- package/template/src/components/template-landing.tsx +2 -2
- package/template/src/lib/showcase-versions.ts +23 -9
- package/template/src/main.tsx +11 -13
package/package.json
CHANGED
|
@@ -125,10 +125,14 @@ jobs:
|
|
|
125
125
|
if: steps.plan.outputs.skip == 'true'
|
|
126
126
|
run: echo "== skip — v${{ steps.plan.outputs.version }} already published"
|
|
127
127
|
|
|
128
|
-
- name: Finalize (commit + push)
|
|
128
|
+
- name: Finalize (versions.json + commit + push)
|
|
129
129
|
run: |
|
|
130
|
+
UPDATE_VERSIONS=""
|
|
131
|
+
if [ "${{ steps.plan.outputs.context }}" = "release" ]; then
|
|
132
|
+
UPDATE_VERSIONS="--update-versions"
|
|
133
|
+
fi
|
|
130
134
|
node scripts/deploy.mjs finalize \
|
|
131
|
-
--site site --push \
|
|
135
|
+
--site site --repo "$REPO_NAME" $UPDATE_VERSIONS --push \
|
|
132
136
|
--commit-message "deploy: ${{ steps.plan.outputs.context }} @ ${{ github.sha }}"
|
|
133
137
|
|
|
134
138
|
cleanup:
|
package/template/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Base template to build a Grist widget with `grist-widget-sdk`.
|
|
|
6
6
|
|
|
7
7
|
Opened outside a Grist iframe, `main.tsx` picks between two components purely by URL shape (`src/lib/showcase-routing.ts`, no router needed): a bare path with no recognized channel suffix renders `TemplateLanding` (its own hero, onboarding, and released-version index); `/latest/`, `/dev/`, or `/v<version>/` renders `ChannelNotice` instead — a distinct hero explaining which build this is, chips to jump to any other version/channel (`src/lib/showcase-versions.ts`), and a copy-this-URL helper for pasting into Grist's custom widget field.
|
|
8
8
|
|
|
9
|
-
When actually embedded, `GristStatusChip` (`src/components/grist-status-chip.tsx`) shows a small pill with live handshake status — connecting, retrying with a countdown, connected, or unavailable — using `
|
|
9
|
+
When actually embedded, `GristStatusChip` (`src/components/grist-status-chip.tsx`) shows a small pill with live handshake status — connecting, retrying with a countdown, connected, or unavailable — using `useAmbientGristHandshake()` from `grist-widget-sdk/advanced`. It must be mounted *inside* `GristWidgetProvider` (see `src/main.tsx`): it only observes that provider's own handshake manager and never mounts a second one, so it's purely observational and can never duplicate or race the real handshake (see `apps/docs/api/handshake.md`).
|
|
10
10
|
|
|
11
11
|
`src/App.tsx` uses `useGrist<TaskRow, TaskMapped>()` for the selected row (`w.record`, `w.mode`) and remounts the UI with `key={rowKey}` when the row changes — same pattern as `widgets/create-email-draft`. See `src/grist-types.example.ts` for typing patterns.
|
|
12
12
|
|
|
@@ -86,6 +86,19 @@ of changes — it's the permanent working/preview branch for this widget, not
|
|
|
86
86
|
a one-off feature branch to delete and recreate. Deleting it retires `/dev/`
|
|
87
87
|
automatically.
|
|
88
88
|
|
|
89
|
+
> ⚠️ **Used GitHub's "Use this template" button instead of `npm create
|
|
90
|
+
> grist-widget`?** That path copies this repo's default branch verbatim —
|
|
91
|
+
> it doesn't run any of the CLI's own setup (renaming `package.json`,
|
|
92
|
+
> titles, etc.), and it won't reset `package.json`'s `version` back to a
|
|
93
|
+
> fresh `0.0.1` for you. Do both by hand before your first real release.
|
|
94
|
+
> Also **never manually seed/copy `gh-pages` content** from another repo
|
|
95
|
+
> (e.g. to get Pages showing something immediately) — each `v<version>/`
|
|
96
|
+
> is only recognized as a genuine prior release if it carries a
|
|
97
|
+
> `showcase-meta.json` naming *this* repo (written by this pipeline's own
|
|
98
|
+
> `place` step); anything else is treated as unpublished and safely
|
|
99
|
+
> rebuilt over, but it's simpler to just let the workflow create
|
|
100
|
+
> `gh-pages` itself (it does, as an orphan branch, on its first real run).
|
|
101
|
+
|
|
89
102
|
**One-time setup** (the workflow can't do this part for you):
|
|
90
103
|
|
|
91
104
|
1. **Settings → Pages** → Source: "Deploy from a branch" → branch `gh-pages`
|
package/template/package.json
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
// release (push to main / workflow_dispatch)
|
|
9
9
|
// - immutable /<repo>/v<version>/ (built once, never overwritten)
|
|
10
10
|
// - mutable /<repo>/latest/ (alias to newest release)
|
|
11
|
+
// - /<repo>/versions.json (released-version index, newest first
|
|
12
|
+
// -- read by the showcase hub/notice)
|
|
11
13
|
//
|
|
12
14
|
// dev (push to dev)
|
|
13
15
|
// - mutable /<repo>/dev/ (+ version.json + self-reload)
|
|
@@ -30,10 +32,19 @@
|
|
|
30
32
|
// Removes dev/ from the gh-pages tree (retire the dev URL when the `dev`
|
|
31
33
|
// branch is deleted). Never touches a release or latest/.
|
|
32
34
|
//
|
|
33
|
-
// finalize --site <dir> [--push] [--commit-message <msg>]
|
|
34
|
-
//
|
|
35
|
+
// finalize --site <dir> [--update-versions] [--push] [--commit-message <msg>]
|
|
36
|
+
// Regenerates versions.json (release runs only) and commits + pushes the
|
|
37
|
+
// gh-pages tree with a rebase-and-retry loop.
|
|
35
38
|
|
|
36
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
existsSync,
|
|
41
|
+
readFileSync,
|
|
42
|
+
readdirSync,
|
|
43
|
+
writeFileSync,
|
|
44
|
+
rmSync,
|
|
45
|
+
mkdirSync,
|
|
46
|
+
cpSync,
|
|
47
|
+
} from "node:fs"
|
|
37
48
|
import { join, dirname } from "node:path"
|
|
38
49
|
import { execFileSync } from "node:child_process"
|
|
39
50
|
|
|
@@ -47,10 +58,33 @@ export function basePathFor(repo, channel, version) {
|
|
|
47
58
|
return `/${repo}/${leaf}/`
|
|
48
59
|
}
|
|
49
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Was `versionDir` actually published by *this* repo's own pipeline? Checks
|
|
63
|
+
* for a `showcase-meta.json` whose `repo` field matches -- not just that the
|
|
64
|
+
* directory exists. A `gh-pages` branch manually seeded from another repo's
|
|
65
|
+
* export (found live: a freshly-created widget repo's `gh-pages` already
|
|
66
|
+
* contained `v<version>/` directories copied from the template repo it came
|
|
67
|
+
* from, some with no meta file at all, one with a foreign `showcase-meta.json`
|
|
68
|
+
* whose `sha` didn't belong to this repo's own history) would otherwise look
|
|
69
|
+
* indistinguishable from a real prior release and get silently treated as
|
|
70
|
+
* "already published" forever, permanently squatting on that version path.
|
|
71
|
+
*/
|
|
72
|
+
function isGenuineRelease(versionDir, repo) {
|
|
73
|
+
const metaPath = join(versionDir, "showcase-meta.json")
|
|
74
|
+
if (!existsSync(metaPath)) return false
|
|
75
|
+
try {
|
|
76
|
+
const meta = JSON.parse(readFileSync(metaPath, "utf8"))
|
|
77
|
+
return meta.repo === repo
|
|
78
|
+
} catch {
|
|
79
|
+
return false
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
50
83
|
/**
|
|
51
84
|
* Build the release/dev plan from this repo's own package.json + the current
|
|
52
|
-
* gh-pages tree. Release is skipped when its immutable v<version> dir
|
|
53
|
-
*
|
|
85
|
+
* gh-pages tree. Release is skipped when its immutable v<version> dir was
|
|
86
|
+
* genuinely published by this repo before (unless force), so re-pushing main
|
|
87
|
+
* without a version bump is a no-op.
|
|
54
88
|
*/
|
|
55
89
|
export function plan(siteDir, repo, event, ref, { force = false } = {}) {
|
|
56
90
|
const isRelease = event === "workflow_dispatch" || ref === "refs/heads/main" || ref === "main"
|
|
@@ -61,7 +95,7 @@ export function plan(siteDir, repo, event, ref, { force = false } = {}) {
|
|
|
61
95
|
const version = pkg.version
|
|
62
96
|
if (!version) throw new Error("package.json has no version")
|
|
63
97
|
const versionDir = join(siteDir, `v${version}`)
|
|
64
|
-
const exists =
|
|
98
|
+
const exists = isGenuineRelease(versionDir, repo)
|
|
65
99
|
return {
|
|
66
100
|
context: "release",
|
|
67
101
|
version,
|
|
@@ -107,6 +141,32 @@ export function injectSelfReload(html, sha) {
|
|
|
107
141
|
return out + snippet
|
|
108
142
|
}
|
|
109
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Scan siteDir for previously-released v<version>/showcase-meta.json files
|
|
146
|
+
* and assemble the versions.json manifest, newest first. Read by the
|
|
147
|
+
* showcase hub (TemplateLanding) and per-channel notice (ChannelNotice) to
|
|
148
|
+
* show this widget's own release history -- not the grist-widget-sdk
|
|
149
|
+
* monorepo's (found live: every scaffolded widget's version index showed
|
|
150
|
+
* the monorepo's versions instead of its own, since showcase-versions.ts
|
|
151
|
+
* used to hardcode that URL). Entries whose meta has no `repo` field, or a
|
|
152
|
+
* `repo` that doesn't match this repo, are skipped -- a `gh-pages` branch
|
|
153
|
+
* manually seeded from another repo's export (found live) would otherwise
|
|
154
|
+
* leak that repo's own version history into this one's index.
|
|
155
|
+
*/
|
|
156
|
+
export function buildVersionsManifest(siteDir, repo) {
|
|
157
|
+
const entries = []
|
|
158
|
+
for (const name of existsSync(siteDir) ? readdirSync(siteDir) : []) {
|
|
159
|
+
if (!/^v\d+\.\d+\.\d+/.test(name)) continue
|
|
160
|
+
const metaPath = join(siteDir, name, "showcase-meta.json")
|
|
161
|
+
if (!existsSync(metaPath)) continue
|
|
162
|
+
const meta = JSON.parse(readFileSync(metaPath, "utf8"))
|
|
163
|
+
if (meta.repo !== repo) continue
|
|
164
|
+
entries.push({ version: meta.version, publishedAt: meta.publishedAt })
|
|
165
|
+
}
|
|
166
|
+
entries.sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt))
|
|
167
|
+
return entries
|
|
168
|
+
}
|
|
169
|
+
|
|
110
170
|
// ----------------------------------------------------------------------------
|
|
111
171
|
// Filesystem / git effects
|
|
112
172
|
// ----------------------------------------------------------------------------
|
|
@@ -118,7 +178,7 @@ function replaceDir(dest, srcDist) {
|
|
|
118
178
|
}
|
|
119
179
|
|
|
120
180
|
/** Place a freshly built dist into the gh-pages tree. */
|
|
121
|
-
export function placeTarget({ siteDir, channel, version, distDir, sha, ref }) {
|
|
181
|
+
export function placeTarget({ siteDir, channel, version, distDir, sha, ref, repo }) {
|
|
122
182
|
if (!existsSync(distDir)) throw new Error(`dist not found: ${distDir}`)
|
|
123
183
|
if (channel === "dev") {
|
|
124
184
|
const devDir = join(siteDir, "dev")
|
|
@@ -144,6 +204,10 @@ export function placeTarget({ siteDir, channel, version, distDir, sha, ref }) {
|
|
|
144
204
|
// so nothing 404s.
|
|
145
205
|
const versionDir = join(siteDir, `v${version}`)
|
|
146
206
|
replaceDir(versionDir, distDir)
|
|
207
|
+
writeFileSync(
|
|
208
|
+
join(versionDir, "showcase-meta.json"),
|
|
209
|
+
JSON.stringify({ version, publishedAt: new Date().toISOString(), sha, repo }, null, 2) + "\n",
|
|
210
|
+
)
|
|
147
211
|
const latestDir = join(siteDir, "latest")
|
|
148
212
|
replaceDir(latestDir, distDir)
|
|
149
213
|
mkdirSync(siteDir, { recursive: true })
|
|
@@ -173,8 +237,12 @@ function sleep(ms) {
|
|
|
173
237
|
while (Date.now() < end) {}
|
|
174
238
|
}
|
|
175
239
|
|
|
176
|
-
/**
|
|
177
|
-
export function finalize({ siteDir, push, commitMessage, remoteBranch = "gh-pages" }) {
|
|
240
|
+
/** Regenerate versions.json (release runs only), then commit + push with rebase-retry. */
|
|
241
|
+
export function finalize({ siteDir, updateVersions, push, commitMessage, remoteBranch = "gh-pages", repo }) {
|
|
242
|
+
if (updateVersions) {
|
|
243
|
+
const versions = buildVersionsManifest(siteDir, repo)
|
|
244
|
+
writeFileSync(join(siteDir, "versions.json"), JSON.stringify(versions, null, 2) + "\n")
|
|
245
|
+
}
|
|
178
246
|
git(["add", "-A"], siteDir)
|
|
179
247
|
const status = git(["status", "--porcelain"], siteDir)
|
|
180
248
|
if (!status) {
|
|
@@ -241,6 +309,7 @@ function main() {
|
|
|
241
309
|
distDir: args.dist,
|
|
242
310
|
sha: args.sha,
|
|
243
311
|
ref: args.ref,
|
|
312
|
+
repo: args.repo,
|
|
244
313
|
})
|
|
245
314
|
console.log("placed:", JSON.stringify(res))
|
|
246
315
|
break
|
|
@@ -253,8 +322,10 @@ function main() {
|
|
|
253
322
|
case "finalize": {
|
|
254
323
|
const res = finalize({
|
|
255
324
|
siteDir: args.site,
|
|
325
|
+
updateVersions: !!args["update-versions"],
|
|
256
326
|
push: !!args.push,
|
|
257
327
|
commitMessage: args["commit-message"],
|
|
328
|
+
repo: args.repo,
|
|
258
329
|
})
|
|
259
330
|
console.log("finalize:", JSON.stringify(res))
|
|
260
331
|
break
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "@/components/ui/card"
|
|
11
11
|
import { Input } from "@/components/ui/input"
|
|
12
12
|
import { channelLabel, type Channel } from "@/lib/showcase-routing"
|
|
13
|
-
import {
|
|
13
|
+
import { devUrl, useVersions, versionUrl } from "@/lib/showcase-versions"
|
|
14
14
|
import { cn } from "@/lib/utils"
|
|
15
15
|
|
|
16
16
|
// Shown instead of the widget tree on a specific channel build
|
|
@@ -44,7 +44,7 @@ function VersionChips({ current }: { current: Channel }) {
|
|
|
44
44
|
latest
|
|
45
45
|
</a>
|
|
46
46
|
)}
|
|
47
|
-
<a href={
|
|
47
|
+
<a href={devUrl()} className={chipClass(current.kind === "dev")}>
|
|
48
48
|
dev
|
|
49
49
|
</a>
|
|
50
50
|
{versions?.map((v) => (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from "react"
|
|
2
|
-
import {
|
|
2
|
+
import { useAmbientGristHandshake, type GristWidgetSnapshot } from "grist-widget-sdk/advanced"
|
|
3
3
|
|
|
4
4
|
import { deriveDisplayStatus, type HandshakeDisplayStatus } from "@/lib/grist-handshake-status"
|
|
5
5
|
import { cn } from "@/lib/utils"
|
|
@@ -83,8 +83,7 @@ function ChipVisual({ display, secondsLeft }: { display: HandshakeDisplayStatus;
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
function RealChipContents() {
|
|
87
|
-
const { snapshot } = useGristHandshakeContext()
|
|
86
|
+
function RealChipContents({ snapshot }: { snapshot: GristWidgetSnapshot }) {
|
|
88
87
|
const display = deriveDisplayStatus(snapshot.lifecycle)
|
|
89
88
|
const retryAttempts = display.kind === "retrying" ? display.attempts : 0
|
|
90
89
|
const secondsLeft = useRetryCountdown(retryAttempts, display.kind === "retrying")
|
|
@@ -93,25 +92,25 @@ function RealChipContents() {
|
|
|
93
92
|
|
|
94
93
|
/**
|
|
95
94
|
* Small pill showing live Grist handshake status while actually embedded in
|
|
96
|
-
* Grist.
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
95
|
+
* Grist. Must be mounted *inside* `<GristWidgetProvider>` -- it only observes
|
|
96
|
+
* that provider's own handshake manager (`useAmbientGristHandshake()`,
|
|
97
|
+
* `grist-widget-sdk/advanced`) and never mounts its own, so it can't ever run
|
|
98
|
+
* a second, competing `grist.ready()` call. Renders nothing outside a
|
|
99
|
+
* provider.
|
|
101
100
|
*/
|
|
102
101
|
export function GristStatusChip({ className }: { className?: string }) {
|
|
102
|
+
const handshake = useAmbientGristHandshake()
|
|
103
|
+
if (!handshake) return null
|
|
103
104
|
return (
|
|
104
|
-
<
|
|
105
|
-
<div
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<RealChipContents />
|
|
113
|
-
</div>
|
|
105
|
+
<div className="pointer-events-none fixed top-2 right-2 z-50">
|
|
106
|
+
<div
|
|
107
|
+
className={cn(
|
|
108
|
+
"flex items-center gap-1.5 rounded-full border bg-background/90 px-2.5 py-1 text-xs text-muted-foreground shadow-sm backdrop-blur",
|
|
109
|
+
className,
|
|
110
|
+
)}
|
|
111
|
+
>
|
|
112
|
+
<RealChipContents snapshot={handshake.snapshot} />
|
|
114
113
|
</div>
|
|
115
|
-
</
|
|
114
|
+
</div>
|
|
116
115
|
)
|
|
117
116
|
}
|
|
@@ -2,7 +2,7 @@ import { useState } from "react"
|
|
|
2
2
|
|
|
3
3
|
import { Button } from "@/components/ui/button"
|
|
4
4
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
5
|
-
import {
|
|
5
|
+
import { devUrl, formatDate, useVersions, versionUrl } from "@/lib/showcase-versions"
|
|
6
6
|
|
|
7
7
|
// The showcase "hub" page: rendered whenever this deploy's URL has no
|
|
8
8
|
// recognized channel suffix (see src/lib/showcase-routing.ts) -- this repo's
|
|
@@ -178,7 +178,7 @@ git push origin dev`}</code>
|
|
|
178
178
|
Released template versions
|
|
179
179
|
</h2>
|
|
180
180
|
<a
|
|
181
|
-
href={
|
|
181
|
+
href={devUrl()}
|
|
182
182
|
className="text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground"
|
|
183
183
|
>
|
|
184
184
|
Live dev preview →
|
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
import { useEffect, useState } from "react"
|
|
2
2
|
|
|
3
|
+
import { parseShowcasePath } from "@/lib/showcase-routing"
|
|
4
|
+
|
|
3
5
|
// Shared by the showcase hub (TemplateLanding) and the per-channel notice
|
|
4
|
-
// (ChannelNotice) -- both need the same released-version list
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
// (ChannelNotice) -- both need the same released-version list. URLs are
|
|
7
|
+
// root-relative to wherever THIS deploy actually lives (derived from
|
|
8
|
+
// window.location.pathname via parseShowcasePath's hubPath), not hardcoded
|
|
9
|
+
// to the grist-widget-sdk monorepo's own showcase: a real scaffolded
|
|
10
|
+
// widget has its own versions.json and its own v<version>/ + dev/ dirs,
|
|
11
|
+
// and previously showed the monorepo's version history and links instead
|
|
12
|
+
// of its own (found live on a real scaffold's landing page).
|
|
13
|
+
function hubPath(): string {
|
|
14
|
+
const path = parseShowcasePath(window.location.pathname).hubPath
|
|
15
|
+
return path.endsWith("/") ? path : `${path}/`
|
|
16
|
+
}
|
|
11
17
|
|
|
12
18
|
export type ReleasedVersion = {
|
|
13
19
|
version: string
|
|
14
20
|
publishedAt: string
|
|
15
21
|
}
|
|
16
22
|
|
|
23
|
+
export function versionsUrl(): string {
|
|
24
|
+
return `${hubPath()}versions.json`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function devUrl(): string {
|
|
28
|
+
return `${hubPath()}dev/`
|
|
29
|
+
}
|
|
30
|
+
|
|
17
31
|
export function versionUrl(version: string): string {
|
|
18
|
-
return
|
|
32
|
+
return `${hubPath()}v${version}/`
|
|
19
33
|
}
|
|
20
34
|
|
|
21
35
|
export function useVersions() {
|
|
@@ -24,7 +38,7 @@ export function useVersions() {
|
|
|
24
38
|
|
|
25
39
|
useEffect(() => {
|
|
26
40
|
let cancelled = false
|
|
27
|
-
fetch(
|
|
41
|
+
fetch(versionsUrl(), { cache: "no-store" })
|
|
28
42
|
.then((res) => (res.ok ? res.json() : Promise.reject(res)))
|
|
29
43
|
.then((data: ReleasedVersion[]) => {
|
|
30
44
|
if (!cancelled) setVersions(data)
|
package/template/src/main.tsx
CHANGED
|
@@ -35,20 +35,18 @@ createRoot(document.getElementById("root")!).render(
|
|
|
35
35
|
<TemplateLanding />
|
|
36
36
|
</div>
|
|
37
37
|
) : isEmbedded ? (
|
|
38
|
-
|
|
38
|
+
<GristWidgetProvider options={GRIST_OPTIONS}>
|
|
39
39
|
<GristStatusChip />
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
>
|
|
44
|
-
<
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
</GristWidgetProvider>
|
|
51
|
-
</>
|
|
40
|
+
<GristBoundary
|
|
41
|
+
gate={GRIST_OPTIONS.columns?.length ? "canRender" : "ready"}
|
|
42
|
+
>
|
|
43
|
+
<div className="min-h-full w-full bg-background text-foreground">
|
|
44
|
+
<GristSdkAlerts>
|
|
45
|
+
<App />
|
|
46
|
+
</GristSdkAlerts>
|
|
47
|
+
</div>
|
|
48
|
+
</GristBoundary>
|
|
49
|
+
</GristWidgetProvider>
|
|
52
50
|
) : (
|
|
53
51
|
<div className="min-h-full w-full bg-background text-foreground">
|
|
54
52
|
<ChannelNotice channel={channel} hubPath={hubPath} />
|