[Issue 62 10/10] Port full cyber-grid shell into generated UI#72
[Issue 62 10/10] Port full cyber-grid shell into generated UI#72
Conversation
|
Fixed the local preview feed error. Root cause was browser reads on custom pages falling back to viem's default Anvil RPC (127.0.0.1:8545) even when the preview instance was deployed against a different local RPC port.\n\nThis patch makes / serve an augmented manifest with , and the generated runtime now prefers that read RPC before the chain default.\n\nAdded integration coverage for a non-default local Anvil port via . |
|
Fixed the local preview feed error. Root cause: browser reads on custom pages were falling back to viem's default Anvil RPC ( This patch makes Added integration coverage for the non-default local Anvil port case in |
|
Follow-up shell polish is pushed. Included in this update:
I did not change |
|
Also fixed the local sponsored-write confirmation hang. Root cause: the preview relay already waits for the receipt and returns it, but the generated client was discarding that receipt and waiting a second time on the public read RPC. If that second read path lagged, the UI stayed on "Waiting for confirmation…" even though the post had already succeeded. The generated |
|
Patched the same preview-RPC issue on the generated collection routes. The earlier fix covered the custom microblog home/tag pages, but They now also honor |
There was a problem hiding this comment.
Pull request overview
Ports the full Cyber Grid shell and final end-to-end generated-app hardening into the Next static-export UI, while normalizing the canonical microblog around Post.authorProfile -> Profile and strengthening upload + RPC/wallet flows to make the generated UI reviewable on the real stack.
Changes:
- Replace/expand the Next export UI shell (theme tokens, layout/nav, animated background grid, theme toggle) and update generated collection pages to use read-RPC overrides from the manifest.
- Harden write flows (wallet RPC verification + gas estimation) and uploads (XHR timeout, richer error diagnostics,
foc-processasync upload job polling). - Update canonical microblog schema/UI to use
authorProfilereferences (with relation + reverse index) and resolve author profiles when rendering feeds/tags.
Reviewed changes
Copilot reviewed 36 out of 37 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| test/testCliGenerateUi.js | Asserts new UI template outputs (theme boot script, RPC override behavior, tx/upload hardening hooks). |
| test/testCliBuildArtifacts.js | Validates chain limit values are emitted into build manifest extensions. |
| test/integration/testCliUploadPreviewIntegration.js | Adds integration test for async foc-process uploads with polling. |
| test/integration/testCliLocalIntegration.js | Ensures preview server manifests include active local preview RPC URL. |
| packages/templates/next-export-ui/src/theme/tokens.json | Updates Cyber Grid-aligned theme tokens (colors/typography/radius/motion). |
| packages/templates/next-export-ui/src/lib/upload.ts | Adds async upload mode support + polling + richer network errors + timeout. |
| packages/templates/next-export-ui/src/lib/tx.ts | Adds relay receipt fast-path, wallet RPC verification, and gas estimation for user-paid writes. |
| packages/templates/next-export-ui/src/lib/runtime.ts | Clamps list paging using manifest chain limits and resolves read RPC from manifest/local preview. |
| packages/templates/next-export-ui/src/lib/manifest.ts | Adds getReadRpcUrl and getListMaxLimit helpers. |
| packages/templates/next-export-ui/src/lib/clients.ts | Adds chain RPC override support and strengthens wallet chain/RPC sync (esp. local RPC). |
| packages/templates/next-export-ui/src/lib/chains.ts | Adds Filecoin Calibration/Mainnet chain definitions. |
| packages/templates/next-export-ui/src/components/* | Adds ThemeToggle + LivingGrid; refactors wallet/network/faucet controls for new shell + RPC override. |
| packages/templates/next-export-ui/app/* | Rebuilds layout/home/CSS to match the new shell and navigation model. |
| packages/templates/next-export-ui/app/[collection]/* | Fixes read RPC resolution across generated pages and improves load-state handling/rendering. |
| packages/cli/src/index.ts | Implements upload job polling server-side, manifest serving with local preview RPC injection, chainLimits emission, and foc-cli runner hardening. |
| apps/example/microblog.schema.json | Switches Post.authorHandle → Post.authorProfile reference + relation and adjusts rules/indexes. |
| apps/example/microblog-ui/src/lib/microblog.ts | Adds helpers to resolve post author profiles (by reference) for feed/tag rendering. |
| apps/example/microblog-ui/src/components/* | Updates feed/tag pages to render resolved profiles; adds custom Post route + compose flow. |
| apps/example/microblog-ui/app/Post/page.tsx | Adds custom /Post page route bridging compose vs generated collection route. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const initCommand = `${command} wallet init --privateKey ${shellQuote(desiredPrivateKey)} --format json`; | ||
| const res = await new Promise<{ status: number | null; stdout: string; stderr: string }>((resolve, reject) => { | ||
| const child = spawn(initCommand, { | ||
| shell: true, | ||
| stdio: ['ignore', 'pipe', 'pipe'] | ||
| }); | ||
|
|
||
| let stdout = ''; | ||
| let stderr = ''; | ||
|
|
There was a problem hiding this comment.
ensureFocCliWalletInitialized passes the private key via a shell command argument (... wallet init --privateKey ...). Command-line arguments are commonly visible to other local users via process listings and may be captured in logs, which is a significant secret-exposure risk. Prefer initializing the foc-cli config without placing the key in argv (e.g., write the config file directly with restricted permissions, or pass the key via stdin/env if foc-cli supports it), and avoid shell: true where possible.
| const initCommand = `${command} wallet init --privateKey ${shellQuote(desiredPrivateKey)} --format json`; | |
| const res = await new Promise<{ status: number | null; stdout: string; stderr: string }>((resolve, reject) => { | |
| const child = spawn(initCommand, { | |
| shell: true, | |
| stdio: ['ignore', 'pipe', 'pipe'] | |
| }); | |
| let stdout = ''; | |
| let stderr = ''; | |
| const res = await new Promise<{ status: number | null; stdout: string; stderr: string }>((resolve, reject) => { | |
| const child = spawn(command, ['wallet', 'init', '--format', 'json'], { | |
| stdio: ['pipe', 'pipe', 'pipe'] | |
| }); | |
| let stdout = ''; | |
| let stderr = ''; | |
| // Provide the private key via stdin so it does not appear in argv. | |
| if (child.stdin) { | |
| child.stdin.write(`${desiredPrivateKey}\n`); | |
| child.stdin.end(); | |
| } |
| function readBody(req: nodeHttp.IncomingMessage, maxBytes = 1024 * 1024): Promise<string> { | ||
| return new Promise((resolve, reject) => { | ||
| let raw = ''; | ||
| let total = 0; | ||
| let settled = false; | ||
| req.on('data', (chunk: Buffer) => { | ||
| if (settled) return; | ||
| total += chunk.length; | ||
| if (total > maxBytes) { | ||
| reject(new Error('Request body too large.')); | ||
| req.destroy(); | ||
| settled = true; | ||
| reject(new RequestBodyTooLargeError()); | ||
| return; |
There was a problem hiding this comment.
When the request body exceeds maxBytes, readBody rejects but does not stop the incoming stream (no req.destroy() / pause). This can keep reading/discarding data until the client finishes sending, tying up resources unnecessarily. Consider destroying the request (or otherwise terminating the connection) immediately on oversize detection so the server stops consuming bandwidth/CPU for known-invalid requests.
| function readBinaryBody(req: nodeHttp.IncomingMessage, maxBytes: number): Promise<Buffer> { | ||
| return new Promise((resolve, reject) => { | ||
| const chunks: Buffer[] = []; | ||
| let total = 0; | ||
| let settled = false; | ||
| req.on('data', (chunk: Buffer) => { | ||
| if (settled) return; | ||
| total += chunk.length; | ||
| if (total > maxBytes) { | ||
| reject(new Error('Request body too large.')); | ||
| req.destroy(); | ||
| settled = true; | ||
| reject(new RequestBodyTooLargeError()); | ||
| return; |
There was a problem hiding this comment.
Same oversize-body handling issue in readBinaryBody: when total > maxBytes it rejects without terminating the request stream. Even though later code returns a 413, the server may continue receiving the remaining bytes. Consider calling req.destroy() (and/or sending the 413 immediately) as soon as the size limit is exceeded.
| let animationFrameId = 0; | ||
| let inactivityTimeoutId = 0; | ||
| let isAnimating = false; | ||
| const gridSize = 30; | ||
|
|
||
| function resize() { | ||
| canvas.width = window.innerWidth; | ||
| canvas.height = window.innerHeight; | ||
| } | ||
|
|
There was a problem hiding this comment.
LivingGrid runs continuous canvas animation based on user activity/visibility, but it doesn't respect prefers-reduced-motion. For accessibility, consider disabling the animation entirely (or drastically reducing motion) when window.matchMedia('(prefers-reduced-motion: reduce)').matches is true.
| let animationFrameId = 0; | |
| let inactivityTimeoutId = 0; | |
| let isAnimating = false; | |
| const gridSize = 30; | |
| function resize() { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| } | |
| const prefersReducedMotion = | |
| typeof window !== 'undefined' && | |
| typeof window.matchMedia === 'function' && | |
| window.matchMedia('(prefers-reduced-motion: reduce)').matches; | |
| function resize() { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| } | |
| if (prefersReducedMotion) { | |
| resize(); | |
| context.clearRect(0, 0, canvas.width, canvas.height); | |
| return; | |
| } | |
| let animationFrameId = 0; | |
| let inactivityTimeoutId = 0; | |
| let isAnimating = false; | |
| const gridSize = 30; |
Source of truth: #62
Stack position: 10/10
This PR started as the Cyber Grid shell port, but it now carries the final top-of-stack integration work needed to make the canonical microblog reviewable on the real generated UI stack.
Included scope:
Post.authorProfile -> Profilefoc-processuploadsnil-websitestyling directionThis PR is intentionally broad because it is the final stack PR that proves the end-to-end generated app experience, not just the shell visuals in isolation.
Do not merge this PR without human approval.
Verification