Skip to content

[Issue 62 10/10] Port full cyber-grid shell into generated UI#72

Merged
snissn merged 18 commits intomasterfrom
issue-62/pr-10-port-cyber-grid-shell
Mar 24, 2026
Merged

[Issue 62 10/10] Port full cyber-grid shell into generated UI#72
snissn merged 18 commits intomasterfrom
issue-62/pr-10-port-cyber-grid-shell

Conversation

@snissn
Copy link
Contributor

@snissn snissn commented Mar 23, 2026

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:

  • port the full Cyber Grid shell into the generated Next export UI
  • normalize the canonical microblog around Post.authorProfile -> Profile
  • fix preview RPC resolution across custom pages and generated collection routes
  • harden local wallet/RPC sync and user-paid write gas handling
  • improve native image upload UX in generated apps
  • harden FOC-backed browser uploads, including async browser upload polling for long-running foc-process uploads
  • align the Cyber Grid theme implementation with the nil-website styling direction

This 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

pnpm build
pnpm mocha test/testCliGenerateUi.js
pnpm mocha test/integration/testCliLocalIntegration.js
pnpm mocha test/integration/testCliUploadPreviewIntegration.js test/testCliUploads.js

@snissn
Copy link
Contributor Author

snissn commented Mar 23, 2026

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 .

@snissn
Copy link
Contributor Author

snissn commented Mar 23, 2026

Fixed the local preview feed error.

Root cause: browser reads on custom pages were falling back to viem's default Anvil RPC (http://127.0.0.1:8545) even when the preview instance had been deployed against a different local RPC port.

This patch makes th preview and th up serve an augmented manifest with extensions.localPreview.rpcUrl, and the generated runtime now prefers that read RPC before the chain default.

Added integration coverage for the non-default local Anvil port case in test/integration/testCliLocalIntegration.js.

@snissn
Copy link
Contributor Author

snissn commented Mar 23, 2026

Follow-up shell polish is pushed.

Included in this update:

  • removed the /tokenhost/ops sublabel from the default header brand
  • scaled the brand treatment up so the logo reads taller in the fixed nav shell
  • aligned the template palette with the upstream Cyber Grid polarity: light mode uses cyan as primary, dark mode uses magenta as primary
  • converted the floating section copy blocks into layered panel sections in both the default template and the canonical microblog home page

I did not change /home/mikers/dev/nil-store/cybergrid-theme because its light/dark primary polarity was already correct; Token Host was the repo that had drifted.

@snissn
Copy link
Contributor Author

snissn commented Mar 23, 2026

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 submitWriteTx helper now uses body.receipt immediately when the relay provides it.

@snissn
Copy link
Contributor Author

snissn commented Mar 23, 2026

Patched the same preview-RPC issue on the generated collection routes.

The earlier fix covered the custom microblog home/tag pages, but /Post/, /Profile/, and the generated create/view/edit/delete clients were still booting reads from ?rpc= or the chain default only.

They now also honor getReadRpcUrl(manifest), so redirects after create/edit/delete stay on the same live preview RPC instead of falling back to 127.0.0.1:8545.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-process async upload job polling).
  • Update canonical microblog schema/UI to use authorProfile references (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.authorHandlePost.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.

Comment on lines +2393 to +2402
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 = '';

Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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();
}

Copilot uses AI. Check for mistakes.
Comment on lines 2756 to 2767
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;
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines 2784 to 2795
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;
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +54
let animationFrameId = 0;
let inactivityTimeoutId = 0;
let isAnimating = false;
const gridSize = 30;

function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}

Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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;

Copilot uses AI. Check for mistakes.
@snissn snissn changed the base branch from issue-62/pr-09-cyber-grid-default-theme to master March 24, 2026 19:13
@snissn snissn merged commit 5c83c56 into master Mar 24, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants