Conversation
Read _folder.json files from block folders and include their metadata (description, icon) in the MetaInfo response, keyed by blockType and folder name. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add gzip compression for large SSE payloads (>50KB) to reduce tunnel latency - Add schema disk caching to avoid regenerating schema on unchanged manifests - Skip manifest.gen.ts write when content unchanged to prevent HMR restarts - Pre-warm meta on daemon startup to avoid waiting for watchMeta() loop - Add decompression support in client for gzipped SSE events This reduces meta-info delivery from 15+ seconds to ~25-65ms by compressing the ~816KB payload to ~123KB before sending through the tunnel. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Two optimizations: 1. Skip walking node_modules, .git, _fresh, etc. using walk's skip option 2. Only read .json files for metadata - .ts/.tsx files can't be blocks Also skip large generated files: schemas.gen.json, manifest.gen.ts, fresh.gen.ts Results: 56ms (down from 470ms) - 8.4x faster Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add X-SSE-Encoding header check for backward compatibility. Old clients without the header will receive uncompressed data. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Two issues fixed: 1. SKIP_DIRS entries like .git, .deno, .cache were not escaped, causing the regex to match any character (e.g., /xdeno/ would match). Now properly escaped as \.git, \.deno, \.cache. 2. Removed followSymlinks:false from walk() options. This was new and could break k8s environments that use symlinks for volumes or mounted files. The default (follow symlinks) is restored. Also added .claude/ to .gitignore. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
SSE improvements: - Wrap enqueue() internals in try/catch to prevent stream crashes - Wrap file sync loop in try/catch to handle startFS errors gracefully - Log errors instead of crashing silently eagerStart improvements: - Add 60s timeout to prevent hanging forever if worker never becomes ready - Log success/failure/timeout for debugging - Errors are now logged instead of silently swallowed Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. SSE: Add X-SSE-Encoding response header when client supports gzip - Admin-cx checks for this header to decide whether to use readFromStream - Without it, gzip detection was broken 2. /_ready: Fix fake timeout that didn't actually abort anything - Replaced unused AbortController with proper Promise.race - Now correctly returns 503 on timeout 3. Schema cache: Add deco version to invalidation key - Cache now invalidates when deco is upgraded - Added timestamp for debugging 4. Cleanup: Remove unused variables - Removed daemonStartTime from main.ts - Removed iteration from meta.ts watchMeta Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. runtime/features/meta.ts: Only ignore NotFound for _folder.json - Other errors (invalid JSON) now logged for debugging 2. daemon/fs/api.ts: Escape SEPARATOR for regex on Windows - Backslash is special in regex, now properly escaped 3. scripts/apps/bundle.lib.ts: Only ignore NotFound when reading manifest - Permission/I/O errors now surface properly 4. daemon/main.ts /_ready endpoint: - Add timeout validation with min/max bounds (1s-120s) - Fix timer leak by clearing setTimeout when worker finishes first Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tagging OptionsShould a new tag be published when this PR is merged?
|
📝 WalkthroughWalkthroughThis PR implements gzip compression for streaming data, optimizes filesystem traversal with skip mechanisms, introduces daemon readiness checks with eager worker startup, adds folder-level metadata support, implements schema caching, and includes conditional manifest writing to avoid unnecessary disk writes. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Daemon
participant Worker
participant Timeout
Client->>Daemon: GET /_ready
activate Daemon
Daemon->>Daemon: Start timeout race
par Worker Startup
Daemon->>Worker: Spawn/initialize worker
activate Worker
Worker->>Worker: Execute startup logic
and Timeout Race
Timeout->>Timeout: Wait (MIN to MAX bounds)
end
alt Worker completes first
Worker->>Daemon: Worker ready
deactivate Worker
Daemon->>Daemon: Trigger /deco/meta (optional)
Daemon->>Client: 200 OK {ready: true}
else Timeout expires first
Timeout->>Daemon: Timeout triggered
Daemon->>Client: 503 Service Unavailable {ready: false, elapsed, error}
end
deactivate Daemon
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
1 issue found across 10 files
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="daemon/main.ts">
<violation number="1" location="daemon/main.ts:447">
P2: Timer leak: The timeout is not cleared when the worker finishes first. Unlike the `/_ready` endpoint which correctly uses `clearTimeout(timeoutId!)`, this Promise.race pattern leaves the 60-second timer running unnecessarily. Apply the same pattern used in `/_ready`.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| } | ||
| return "success"; | ||
| })(), | ||
| new Promise<"timeout">((resolve) => |
There was a problem hiding this comment.
P2: Timer leak: The timeout is not cleared when the worker finishes first. Unlike the /_ready endpoint which correctly uses clearTimeout(timeoutId!), this Promise.race pattern leaves the 60-second timer running unnecessarily. Apply the same pattern used in /_ready.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At daemon/main.ts, line 447:
<comment>Timer leak: The timeout is not cleared when the worker finishes first. Unlike the `/_ready` endpoint which correctly uses `clearTimeout(timeoutId!)`, this Promise.race pattern leaves the 60-second timer running unnecessarily. Apply the same pattern used in `/_ready`.</comment>
<file context>
@@ -351,9 +420,55 @@ const stableEnvironmentName = () => {
+ }
+ return "success";
+ })(),
+ new Promise<"timeout">((resolve) =>
+ setTimeout(() => resolve("timeout"), EAGER_START_TIMEOUT_MS)
+ ),
</file context>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@scripts/dev.ts`:
- Around line 25-33: The TypeScript error TS7053 occurs because the JSON-typed
imports object lacks an index signature, so using the string variable dep to
index it is invalid; cast imports to a string-keyed map before indexing (e.g.,
const importMap = imports as Record<string, unknown> or as any) and then replace
imports[dep] with importMap[dep] inside the loop that iterates requiredDeps
(referencing requiredDeps, dep, imports, entries, isReset) so the assignment
entries[dep] = importMap[dep] compiles without error.
🧹 Nitpick comments (3)
engine/schema/reader.ts (2)
17-28: Consider: hashing only keys may cause stale cache when block content changes.The
hashManifestfunction only hashes block keys, not values. If a block's implementation changes (e.g., different props/types) but the key remains the same, the cache won't invalidate.The
decoVersioncheck provides some protection, but local development changes to blocks won't trigger cache invalidation until a version bump.Is this intentional, relying on version bumps to handle content changes? If local development scenarios need fresh schemas after block modifications, you may want to include file modification times or a content hash.
80-81: Minor: fire-and-forget cache save won't surface write errors.The
saveSchemaCachecall on line 81 is intentionally not awaited, which is fine for a non-critical cache. However, if debugging cache issues, consider adding a debug log inside the catch block ofsaveSchemaCache.runtime/features/meta.ts (1)
88-103: Consider parallelizing file reads for folders.The loop reads
_folder.jsonfiles sequentially. For projects with many folders, parallelizing these reads could reduce latency:♻️ Suggested parallel reads
// Read _folder.json for each folder - for (const folder of folders) { - try { - const filePath = `./${blockType}/${folder}/_folder.json`; - const content = await Deno.readTextFile(filePath); - const meta = JSON.parse(content) as FolderMeta; - if (meta.description || meta.icon) { - folderMeta[blockType] ??= {}; - folderMeta[blockType][folder] = meta; - } - } catch (err) { - // Only ignore missing files, surface other errors (e.g., invalid JSON) - if (!(err instanceof Deno.errors.NotFound)) { - console.error(`[meta] Error reading ${blockType}/${folder}/_folder.json:`, err); - } - } - } + await Promise.all([...folders].map(async (folder) => { + try { + const filePath = `./${blockType}/${folder}/_folder.json`; + const content = await Deno.readTextFile(filePath); + const meta = JSON.parse(content) as FolderMeta; + if (meta.description || meta.icon) { + folderMeta[blockType] ??= {}; + folderMeta[blockType][folder] = meta; + } + } catch (err) { + if (!(err instanceof Deno.errors.NotFound)) { + console.error(`[meta] Error reading ${blockType}/${folder}/_folder.json:`, err); + } + } + }));
| // Copy dependencies that the deco package needs but projects might not have | ||
| const requiredDeps = ["@deco/deno-ast-wasm"]; | ||
| for (const dep of requiredDeps) { | ||
| if (isReset) { | ||
| delete entries[dep]; | ||
| } else if (imports[dep] && !entries[dep]) { | ||
| entries[dep] = imports[dep]; | ||
| } | ||
| } |
There was a problem hiding this comment.
Fix TypeScript error: indexing typed object with string variable.
The pipeline is failing with TS7053 because imports has a specific object type (from the JSON import) without an index signature. Using dep (a string) to access imports[dep] is not allowed.
🐛 Proposed fix: cast imports to allow string indexing
// Copy dependencies that the deco package needs but projects might not have
const requiredDeps = ["@deco/deno-ast-wasm"];
+const importsRecord = imports as Record<string, string>;
for (const dep of requiredDeps) {
if (isReset) {
delete entries[dep];
- } else if (imports[dep] && !entries[dep]) {
- entries[dep] = imports[dep];
+ } else if (importsRecord[dep] && !entries[dep]) {
+ entries[dep] = importsRecord[dep];
}
}🧰 Tools
🪛 GitHub Actions: Publish
[error] 30-30: TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ "$fresh/": string; "@cliffy/prompt": string; "@core/asyncutil": string; "@deco/codemod-toolkit": string; "@deco/deno-ast-wasm": string; "@deco/durable": string; "@deco/warp": string; "@hono/hono": string; ... 23 more ...; "std/": string; }'. No index signature with a parameter of type 'string' was found on type '{ "$fresh/": string; "@cliffy/prompt": string; "@core/asyncutil": string; "@deco/codemod-toolkit": string; "@deco/deno-ast-wasm": string; "@deco/durable": string; "@deco/warp": string; "@hono/hono": string; ... 23 more ...; "std/": string; }'.
[error] 31-31: TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ "$fresh/": string; "@cliffy/prompt": string; "@core/asyncutil": string; "@deco/codemod-toolkit": string; "@deco/deno-ast-wasm": string; "@deco/durable": string; "@deco/warp": string; "@hono/hono": string; ... 23 more ...; "std/": string; }'. No index signature with a parameter of type 'string' was found on type '{ "$fresh/": string; "@cliffy/prompt": string; "@core/asyncutil": string; "@deco/codemod-toolkit": string; "@deco/deno-ast-wasm": string; "@deco/durable": string; "@deco/warp": string; "@hono/hono": string; ... 23 more ...; "std/": string; }'.
🤖 Prompt for AI Agents
In `@scripts/dev.ts` around lines 25 - 33, The TypeScript error TS7053 occurs
because the JSON-typed imports object lacks an index signature, so using the
string variable dep to index it is invalid; cast imports to a string-keyed map
before indexing (e.g., const importMap = imports as Record<string, unknown> or
as any) and then replace imports[dep] with importMap[dep] inside the loop that
iterates requiredDeps (referencing requiredDeps, dep, imports, entries, isReset)
so the assignment entries[dep] = importMap[dep] compiles without error.
Summary by cubic
Cuts meta sync time from 15s+ to ~25–65ms by gzipping SSE, caching schemas, and pre-warming meta. Also adds folder metadata to /deco/meta and a readiness endpoint.
Performance
New Features
Written for commit 97e7a65. Summary will update on new commits.
Summary by CodeRabbit
Release Notes
New Features
Performance Improvements
Chores