fix: ship polished stable Drops Studio release - #18
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe PR updates Drops Studio branding, responsive layouts, platform evidence access, Project V2 refresh and preview behavior, persistence, integration icons, and related tests and QA records. ChangesStudio presentation and platform surfaces
Access verification and evidence views
Project V2 refresh and generated templates
Persistence and validation contracts
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
components/dropsbot-webhook-connection.tsx (1)
69-138: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPrevent stale refresh results during callback writes.
refreshVersiononly orders concurrentrefresh()calls. A manual refresh can start whilecreateCallback()ormutateCallback()is pending. Its old 404 or event response can then apply after the write completes. This can re-enable callback creation after creation or restore evidence after revocation.Use one operation-generation or cancellation mechanism for refreshes and writes. Disable refresh while
creatingormutatingis set. Add deferred-response tests for create, rotate, and revoke.Also applies to: 151-152, 192-193
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/dropsbot-webhook-connection.tsx` around lines 69 - 138, Unify refresh and callback-write concurrency in the component by using a shared operation generation or cancellation mechanism across refresh, createCallback, and mutateCallback, so responses started before a write cannot update state afterward. Disable the manual refresh control while creating or mutating, and ensure create, rotate, and revoke flows invalidate or supersede pending refresh responses while preserving the existing loading and protected-state behavior.app/styles/drops-studio.previews.css (1)
152-156: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winSet
.catcher-result buttonfont size to at least 14px. The rule atapp/styles/drops-studio.previews.css:155sets control text to 12px. Later size rules do not change its font size.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/styles/drops-studio.previews.css` around lines 152 - 156, Update the `.catcher-result button` rule in the catcher result styling to use a font size of at least 14px, replacing the current 12px value while preserving its existing layout and visual properties.
🧹 Nitpick comments (4)
components/project-studio.tsx (1)
528-559: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDerive
totalfrom the task list plus the preview check.
totalis hardcoded to5in two places while the check set isPROJECT_V2_BUILD_TASKS.length + 1. If the task list changes, the reported counts become wrong.♻️ Proposed refactor
+const PROJECT_V2_BUILD_CHECK_TOTAL = PROJECT_V2_BUILD_TASKS.length + 1; + function projectV2BuildEvidence(projectV2?: ProjectV2): { passed: number; total: number; verified: boolean; } { - if (!projectV2) return { passed: 0, total: 5, verified: false }; + if (!projectV2) { + return { passed: 0, total: PROJECT_V2_BUILD_CHECK_TOTAL, verified: false }; + } @@ - return { passed, total: 5, verified: passed === 5 }; + return { + passed, + total: PROJECT_V2_BUILD_CHECK_TOTAL, + verified: passed === PROJECT_V2_BUILD_CHECK_TOTAL, + }; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/project-studio.tsx` around lines 528 - 559, Update projectV2BuildEvidence to derive the total count from PROJECT_V2_BUILD_TASKS.length plus one preview check, replacing both hardcoded 5 values while preserving the existing passed and verified behavior.tests/runtime-preview-security.test.mjs (1)
99-104: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winExtend the assertions with an origin check for the remote preview.
These assertions lock in
allow-same-originfor URL-based previews and verify only thehttps:protocol check. They do not verify that the preview URL is rejected when it matches the Studio origin. That gap is the reason the risk raised oncomponents/project-studio.tsxlines 4784-4801 is not caught here. After the origin guard is added, assert it in this test.💚 Proposed assertion
assert.match(studio, /function currentProjectV2PreviewUrl/); assert.match(studio, /url\.protocol === "https:"/); + assert.match(studio, /url\.origin === window\.location\.origin/); assert.doesNotMatch(studio, /allow-popups/);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/runtime-preview-security.test.mjs` around lines 99 - 104, Extend the assertions around currentProjectV2PreviewUrl to verify the remote preview origin guard, not only the existing https: protocol check. Assert that the preview URL is rejected when its origin matches the Studio origin, while preserving the existing sandbox attribute and protocol assertions.lib/project-template-ui.ts (1)
463-468: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMake the theme injection fail loudly.
projectTemplateGlobalCssreplaces one exact:root { … }string. IfPROJECT_TEMPLATE_GLOBAL_CSSchanges even by one character,String.prototype.replacereturns the stylesheet unchanged and every generated project silently falls back to the default accent, surface, radius, and font. Extract the default block into a named constant and assert that the replacement occurred.♻️ Proposed refactor
+const PROJECT_TEMPLATE_ROOT_DEFAULTS = + ':root { color-scheme: dark; --project-accent: `#67e8f9`; --project-surface: `#070a12`; --project-radius: 20px; --project-font: Inter, ui-sans-serif, system-ui, sans-serif; }'; + export function projectTemplateGlobalCss(spec: GeneratedProjectSpec): string { - return PROJECT_TEMPLATE_GLOBAL_CSS.replace( - ':root { color-scheme: dark; --project-accent: `#67e8f9`; --project-surface: `#070a12`; --project-radius: 20px; --project-font: Inter, ui-sans-serif, system-ui, sans-serif; }', - `:root { color-scheme: dark; --project-accent: ${spec.theme.accent}; --project-surface: ${spec.theme.surface}; --project-radius: ${spec.design.radius}px; --project-font: ${projectFontStack(spec.design.font)}; }`, - ); + if (!PROJECT_TEMPLATE_GLOBAL_CSS.includes(PROJECT_TEMPLATE_ROOT_DEFAULTS)) { + throw new Error("The generated stylesheet no longer exposes the themable :root block."); + } + return PROJECT_TEMPLATE_GLOBAL_CSS.replace( + PROJECT_TEMPLATE_ROOT_DEFAULTS, + `:root { color-scheme: dark; --project-accent: ${spec.theme.accent}; --project-surface: ${spec.theme.surface}; --project-radius: ${spec.design.radius}px; --project-font: ${projectFontStack(spec.design.font)}; }`, + ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/project-template-ui.ts` around lines 463 - 468, Update projectTemplateGlobalCss and PROJECT_TEMPLATE_GLOBAL_CSS handling to extract the expected default :root block into a named constant, reuse it for replacement, and verify that the stylesheet changed; throw an explicit error when the replacement target is missing so theme injection cannot silently fall back to defaults.tests/project-v2-migration.test.mjs (1)
68-124: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd coverage for preserved manual files and for run history.
The test asserts metadata preservation only. The adapter's stated purpose is to keep manual and AI-authored files while regenerating generated files, and to keep
runs,logs, andcheckpoints. Two gaps remain:
- No file with
provenanceother thangeneratedexists inconfigured, so the preservation branch atlib/project-v2-migration.tslines 356-375 is never exercised.configured.runsstays empty, so the case where a preserved run references a task id that the refreshed baseline does not define is untested. That case is the failure raised onlib/project-v2-migration.tslines 378-399.Add one manual file and one run plus matching task to the fixture.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/project-v2-migration.test.mjs` around lines 68 - 124, Add coverage for two untested preservation branches in the refreshLegacyProjectV2Migration function by extending the configured fixture used in this test. First, add one file to the configured.files object with a provenance property set to a value other than "generated" (such as "manual") to exercise the file preservation code path at lib/project-v2-migration.ts lines 356-375. Second, add at least one run object to configured.runs with a taskId that matches an existing task in configured.tasks, then verify in the test assertions that this run and its associated task are preserved in the refreshed result to cover the runs preservation validation at lib/project-v2-migration.ts lines 378-399.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/styles/project-studio.runtime.css`:
- Around line 6-14: The stage toolbar and zoom controls do not meet
accessibility standards for text size and interactive target dimensions. In
app/styles/project-studio.runtime.css lines 6-14, increase the .stage-toolbar
button font-size from 12px to 14px, increase the .stage-toolbar button
min-height from 29px to 44px to meet the minimum interactive target size, and
increase the .canvas-zoom-controls button min-width from 38px to 44px. In
app/styles/project-studio.workspace.css line 29, remove the 12px font-size
override or increase it to 14px to align with the minimum text size requirement.
In `@app/styles/project-studio.workspace.css`:
- Line 29: Update the .stage-toolbar button rule to use a font size of at least
14px instead of 12px, while preserving the existing toolbar sizing and spacing.
In `@components/platform/integration-catalog.tsx`:
- Around line 12-19: Replace the provider icon imports in the integration
catalog with project-local image assets rendered through next/image for official
provider marks, and use lucide-react for any generic symbols. Remove the
SiAnthropic, SiGithub, SiMoonshotai, SiOpenrouter, SiVercel, and TbBrandOpenai
react-icons imports while preserving the existing provider display behavior.
In `@components/platform/platform-shell.tsx`:
- Around line 67-71: Update the footer description paragraph that begins with
"Real crypto products powered by DropsTab..." to use body-size text styling.
Replace the `text-sm` class in the `<p>` element's className with `text-base` to
achieve the intended 16-18 px font size for the description text.
In `@components/preview-canvas.tsx`:
- Around line 290-294: Add visible text indicators to the eight preview variants
that currently lack data-mode labels: EnginePreview, PredictionPreview,
CopyPreview, AggregatorPreview, GamePreview, CompanionPreview,
TamagotchiPreview, and HuntPreview. For each variant, render an in-frame label
(such as "Sample data" or "Live data") conditionally based on the dataMode state
variable, similar to the existing implementation in ChannelPreview and
RadioPreview. This ensures sighted users can see whether the preview is
displaying live or sample data, matching the information already provided in the
aria-label attribute on the section wrapper.
In `@components/project-studio.tsx`:
- Around line 1332-1339: Update the browserTelemetryReady calculation in the
project studio status logic so it is true only when the browser smoke result is
executed in browser mode with runtime evidence; do not include
runtimePreviewUrl. Keep preview readiness separate and use a preview-specific
status label when only the URL is available, without treating either state as
provider evidence.
- Around line 4784-4801: Update currentProjectV2PreviewUrl validation to reject
HTTPS URLs whose origin matches window.location.origin, allowing only
foreign-origin preview URLs to reach the iframe. Preserve valid external preview
handling, and ensure the iframe sandbox in the runtimePreviewUrl path never
grants allow-same-origin to a same-origin URL.
In `@design-qa.md`:
- Around line 3-16: Update the visual evidence section in design-qa.md to link
matched reference-and-actual capture pairs for both 1024 px and 390 px
viewports, using browser zoom 100% and device scale factor 1. Retain the
existing desktop evidence, and only keep “final result: passed” after both
required viewport pairs are documented.
In `@lib/project-store.ts`:
- Around line 156-171: Update the save flow around the compact index and
canonical item writes so each entry in evictedItems is removed immediately after
writing compatibilityIndex and before writing the new canonical item, recording
removedItems for rollback. Adjust the catch rollback order to restore the
canonical item first, then removed items, and finally the original
PROJECTS_STORAGE_KEY index. Add a quota test covering a new project save that
requires eviction.
In `@lib/project-template-materializer.ts`:
- Around line 397-407: The metadata merge around next.manifest must preserve
consistency with a retained manually edited package.json. After applying the
fresh metadata in this refresh flow, re-synchronize manifest from the resulting
package.json using the existing syncManifestFromPackage behavior, or avoid
overwriting package.json-derived manifest fields while merging fresh.manifest.
- Around line 339-359: The refresh adapters build operation arrays without
enforcing FILE_OPERATION_LIMIT before calling applyProjectV2FileOperations. In
lib/project-template-materializer.ts lines 339-359, update
refreshGeneratedProjectV2Template to validate or batch operations before the
applyProjectV2FileOperations call; in lib/project-v2-migration.ts lines 368-375,
apply the same limit handling to preservedOperations in
refreshLegacyProjectV2Migration. Ensure neither adapter passes more than
FILE_OPERATION_LIMIT operations in a single call.
In `@lib/project-template-ui.ts`:
- Line 396: Update the generated body-copy styles in the radio player and
archive sections: change the `.radio-player > p:not(.radio-track-label,
.radio-playback)` and `.radio-archive p` font sizes to comply with the required
16–18px body-text range, while leaving helper and metadata styles unchanged.
- Around line 181-202: Import useEffect in the generated component template and
add an unmount cleanup effect that calls window.speechSynthesis.cancel() when
browser speech is available. Place the cleanup alongside togglePlayback so any
queued or active utterance stops when the component unmounts, while preserving
the existing playback behavior.
In `@lib/project-v2-migration.ts`:
- Around line 378-399: Update the ProjectV2 construction in the migration
refresh flow to keep task references consistent with preserved runs, logs, and
checkpoints. Preserve current.tasks alongside the existing current state fields,
or filter out persisted runs and related logs whose task IDs are absent from
refreshed.tasks before validation; ensure validateProjectV2 cannot encounter
unknown task references.
---
Outside diff comments:
In `@app/styles/drops-studio.previews.css`:
- Around line 152-156: Update the `.catcher-result button` rule in the catcher
result styling to use a font size of at least 14px, replacing the current 12px
value while preserving its existing layout and visual properties.
In `@components/dropsbot-webhook-connection.tsx`:
- Around line 69-138: Unify refresh and callback-write concurrency in the
component by using a shared operation generation or cancellation mechanism
across refresh, createCallback, and mutateCallback, so responses started before
a write cannot update state afterward. Disable the manual refresh control while
creating or mutating, and ensure create, rotate, and revoke flows invalidate or
supersede pending refresh responses while preserving the existing loading and
protected-state behavior.
---
Nitpick comments:
In `@components/project-studio.tsx`:
- Around line 528-559: Update projectV2BuildEvidence to derive the total count
from PROJECT_V2_BUILD_TASKS.length plus one preview check, replacing both
hardcoded 5 values while preserving the existing passed and verified behavior.
In `@lib/project-template-ui.ts`:
- Around line 463-468: Update projectTemplateGlobalCss and
PROJECT_TEMPLATE_GLOBAL_CSS handling to extract the expected default :root block
into a named constant, reuse it for replacement, and verify that the stylesheet
changed; throw an explicit error when the replacement target is missing so theme
injection cannot silently fall back to defaults.
In `@tests/project-v2-migration.test.mjs`:
- Around line 68-124: Add coverage for two untested preservation branches in the
refreshLegacyProjectV2Migration function by extending the configured fixture
used in this test. First, add one file to the configured.files object with a
provenance property set to a value other than "generated" (such as "manual") to
exercise the file preservation code path at lib/project-v2-migration.ts lines
356-375. Second, add at least one run object to configured.runs with a taskId
that matches an existing task in configured.tasks, then verify in the test
assertions that this run and its associated task are preserved in the refreshed
result to cover the runs preservation validation at lib/project-v2-migration.ts
lines 378-399.
In `@tests/runtime-preview-security.test.mjs`:
- Around line 99-104: Extend the assertions around currentProjectV2PreviewUrl to
verify the remote preview origin guard, not only the existing https: protocol
check. Assert that the preview URL is rejected when its origin matches the
Studio origin, while preserving the existing sandbox attribute and protocol
assertions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 16bfa465-0d9e-4ffe-8ea4-5746d7276960
⛔ Files ignored due to path filters (5)
docs/design/current-home-actual.pngis excluded by!**/*.pngdocs/design/current-integrations-actual.pngis excluded by!**/*.pngdocs/design/current-integrations-reference-vs-actual.pngis excluded by!**/*.pngdocs/design/current-studio-actual.pngis excluded by!**/*.pngpackage-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (42)
app/internal/agent-evals/page.tsxapp/layout.tsxapp/page.tsxapp/styles/drops-brand.cssapp/styles/drops-studio.builder.cssapp/styles/drops-studio.previews.cssapp/styles/drops-studio.responsive.cssapp/styles/project-studio.inspector.cssapp/styles/project-studio.runtime.cssapp/styles/project-studio.workspace.csscomponents/agent-eval-dashboard.module.csscomponents/agent-eval-dashboard.tsxcomponents/drops-brand.tsxcomponents/drops-studio.tsxcomponents/dropsbot-webhook-connection.tsxcomponents/platform/integration-catalog.tsxcomponents/platform/platform-shell.tsxcomponents/preview-canvas.tsxcomponents/project-studio.tsxcomponents/studio-account-team-panel.tsxdesign-qa.mde2e/contracts/home-builder-p1.spec.tse2e/contracts/project-v2-studio.spec.tse2e/contracts/release-boundaries.spec.tse2e/proofs/director-flow.spec.tslib/enterprise-platform/oidc-provider-route.tslib/project-store.tslib/project-template-materializer.tslib/project-template-ui.tslib/project-v2-migration.tspackage.jsontests/agent-context-rag.test.mjstests/artifact-security.test.mjstests/collaboration-transport-client.test.mjstests/dropsbot-webhook.test.mjstests/product-reality.test.mjstests/project-export-portability.test.mjstests/project-store.test.mjstests/project-template-materializer.test.mjstests/project-v2-migration.test.mjstests/runtime-preview-security.test.mjstests/studio-account-team-panel.test.mjs
💤 Files with no reviewable changes (1)
- app/page.tsx
| .stage-toolbar button { align-items: center; background: transparent; border: 1px solid transparent; border-radius: 7px; color: #718199; display: flex; font-size: 12px; font-weight: 710; gap: 5px; min-height: 29px; padding: 0 8px; } | ||
| .stage-toolbar button:hover,.stage-toolbar button.active { background: white; border-color: #d4deeb; color: #2f63c8; } | ||
| .stage-toolbar button.quality-ready { background: #e8f8f1; border-color: #bfe4d4; color: #16875b; } | ||
| .stage-toolbar svg { height: 12px; width: 12px; } | ||
| .device-switch { background: #e3e9f2; border-radius: 8px; padding: 2px; } | ||
| .device-switch button.active { box-shadow: 0 2px 5px rgba(28,46,75,.1); } | ||
| .runtime-browser { background: white; border: 1px solid #d4dfec; border-radius: 14px; box-shadow: 0 14px 36px rgba(28,48,80,.1); height: calc(100% - 46px); margin: auto; min-height: 590px; overflow: auto; transition: .2s ease; width: 100%; } | ||
| .canvas-zoom-controls { align-items: center; background: #e3e9f2; border-radius: 8px; display: inline-flex; flex: 0 0 auto; padding: 2px; } | ||
| .canvas-zoom-controls button { justify-content: center; min-width: 38px; padding-inline: 7px; } | ||
| .canvas-zoom-controls button:nth-child(2) { min-width: 58px; } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Make the shared stage toolbar accessible.
Both style layers set stage-toolbar text below 14px. The runtime layer also permits targets smaller than 44px by 44px.
app/styles/project-studio.runtime.css#L6-L14: set toolbar text to at least 14px and set every toolbar and zoom button to at least 44px by 44px.app/styles/project-studio.workspace.css#L29-L29: remove the 12px override or raise it to at least 14px.
As per coding guidelines, “control text [must be] at least 14 px” and “Every visible interactive target must be at least 44 by 44 CSS pixels.”
📍 Affects 2 files
app/styles/project-studio.runtime.css#L6-L14(this comment)app/styles/project-studio.workspace.css#L29-L29
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/styles/project-studio.runtime.css` around lines 6 - 14, The stage toolbar
and zoom controls do not meet accessibility standards for text size and
interactive target dimensions. In app/styles/project-studio.runtime.css lines
6-14, increase the .stage-toolbar button font-size from 12px to 14px, increase
the .stage-toolbar button min-height from 29px to 44px to meet the minimum
interactive target size, and increase the .canvas-zoom-controls button min-width
from 38px to 44px. In app/styles/project-studio.workspace.css line 29, remove
the 12px font-size override or increase it to 14px to align with the minimum
text size requirement.
Source: Coding guidelines
| .checkpoint-list { gap: 9px; }.checkpoint-list > button { padding: 12px; }.checkpoint-list strong { font-size: 12px; }.checkpoint-list small { font-size: 12px; }.checkpoint-list > button > b { font-size: 12px; } | ||
| .runtime-stage { background: #eef2f8; overflow: auto; padding: 13px 16px 16px; }.stage-toolbar { height: 44px; margin-bottom: 10px; }.stage-toolbar button { font-size: 12px; min-height: 44px; padding: 0 10px; }.stage-toolbar svg { height: 14px; width: 14px; } | ||
| .runtime-browser { height: calc(100% - 54px); min-height: 600px; }.browser-bar { height: 38px; }.browser-bar > strong { font-size: 12px; min-width: 180px; }.browser-bar > b { font-size: 12px; }.runtime-browser iframe { height: calc(100% - 38px); } | ||
| .runtime-stage { background: #eef2f8; overflow: hidden; padding: 10px 12px 12px; }.stage-toolbar { height: 44px; margin-bottom: 10px; }.stage-toolbar button { font-size: 12px; min-height: 44px; padding: 0 10px; }.stage-toolbar svg { height: 14px; width: 14px; } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use at least 14px toolbar text.
Line 29 overrides toolbar button text to 12px. Increase it to at least 14px.
As per coding guidelines, “control text [must be] at least 14 px.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/styles/project-studio.workspace.css` at line 29, Update the
.stage-toolbar button rule to use a font size of at least 14px instead of 12px,
while preserving the existing toolbar sizing and spacing.
Source: Coding guidelines
| import { | ||
| SiAnthropic, | ||
| SiGithub, | ||
| SiMoonshotai, | ||
| SiOpenrouter, | ||
| SiVercel, | ||
| } from "react-icons/si"; | ||
| import { TbBrandOpenai } from "react-icons/tb"; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
printf '%s\n' '--- status ---'
git status -sb
printf '%s\n' '--- target file ---'
cat -n components/platform/integration-catalog.tsx | sed -n '1,120p'
printf '%s\n' '--- icon dependencies and usages ---'
rg -n '"(lucide-react|react-icons)"|from "react-icons|from '\''react-icons|SiAnthropic|SiGithub|SiMoonshotai|SiOpenrouter|SiVercel|TbBrandOpenai' --glob '!node_modules/**' --glob '!dist/**' .
printf '%s\n' '--- project-local image/icon conventions ---'
rg -n 'next/image|<Image|lucide-react|Icon' components app src 2>/dev/null | head -200Repository: svg8bit/drops-studio
Length of output: 25537
🏁 Script executed:
printf '%s\n' '--- provider assets ---'
fd -HI -t f . public 2>/dev/null | rg -i '(openai|anthropic|openrouter|moonshot|kimi|github|vercel|provider|brand|logo)'
printf '%s\n' '--- deterministic import/use check ---'
python3 - <<'PY'
from pathlib import Path
p = Path("components/platform/integration-catalog.tsx")
text = p.read_text()
react_icons = [line.strip() for line in text.splitlines() if "react-icons" in line]
lucide = [line.strip() for line in text.splitlines() if 'from "lucide-react"' in line]
print({"react_icons_imports": react_icons, "lucide_imports": lucide})
print("react_icons_used_for_catalog_entries:",
all(name in text for name in ("TbBrandOpenai", "SiAnthropic", "SiGithub",
"SiMoonshotai", "SiOpenrouter", "SiVercel")))
PYRepository: svg8bit/drops-studio
Length of output: 514
Replace the react-icons provider marks.
Use project-local image assets with next/image for official marks. Use lucide-react for generic symbols. Remove the react-icons imports.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/platform/integration-catalog.tsx` around lines 12 - 19, Replace
the provider icon imports in the integration catalog with project-local image
assets rendered through next/image for official provider marks, and use
lucide-react for any generic symbols. Remove the SiAnthropic, SiGithub,
SiMoonshotai, SiOpenrouter, SiVercel, and TbBrandOpenai react-icons imports
while preserving the existing provider display behavior.
Source: Coding guidelines
| <div className="mx-auto grid w-full max-w-[1500px] gap-8 px-4 py-10 sm:px-6 md:grid-cols-[minmax(220px,.8fr)_minmax(0,1.2fr)] lg:px-8"> | ||
| <div className="flex min-w-0 flex-col items-start gap-4"> | ||
| <DropsBrand compact /> | ||
| <p className="max-w-sm text-sm leading-6 text-[#52617a]">Real crypto products powered by DropsTab intelligence and approved Drops Bot delivery.</p> | ||
| <span className="inline-flex items-start gap-2 rounded-xl bg-[#eef4ff] p-3 text-xs leading-5 text-[#52617a]"><ShieldCheck className="mt-0.5 size-4 shrink-0 text-[#245fe5]" aria-hidden="true" />Provider and deployment states appear only with matching evidence.</span> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- target lines ---'
sed -n '60,75p' components/platform/platform-shell.tsx
printf '%s\n' '--- Tailwind/config files ---'
git ls-files | grep -E '(^|/)(tailwind|postcss|components.json|package.json)' | head -80
printf '%s\n' '--- text-sm definitions/usages ---'
rg -n --glob '!node_modules' --glob '!dist' --glob '!build' 'text-sm|font-size' .Repository: svg8bit/drops-studio
Length of output: 50377
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- package and Tailwind entry ---'
cat package.json
cat app/styles/tailwind.css
printf '%s\n' '--- global typography tokens and overrides ---'
cat app/styles/drops-studio.guardrails.css
printf '%s\n' '--- relevant selectors for the footer description ---'
rg -n -C 3 'footer|font-size|--font-body|text-sm' components/platform/platform-shell.tsx app/stylesRepository: svg8bit/drops-studio
Length of output: 50377
Use body-size text for the footer description.
Line 70 renders at 14 px because text-sm is not overridden outside .studio-shell. Use text-base or an equivalent 16–18 px token.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/platform/platform-shell.tsx` around lines 67 - 71, Update the
footer description paragraph that begins with "Real crypto products powered by
DropsTab..." to use body-size text styling. Replace the `text-sm` class in the
`<p>` element's className with `text-base` to achieve the intended 16-18 px font
size for the description text.
Source: Coding guidelines
| <section | ||
| className="preview-column" | ||
| aria-label={`${visibleName} ${dataMode === "live" ? "with live DropsTab data" : "with labelled sample data"}`} | ||
| aria-live="polite" | ||
| > |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 4 'dataMode|sample data|live DropsTab|labelled sample' \
components/preview-canvas.tsx \
components/preview-canvas-variants.tsx \
components/brief-preview.tsx 2>/dev/null || trueRepository: svg8bit/drops-studio
Length of output: 13529
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find all preview variant types by looking at the switch statement routing
echo "=== All preview variant cases ==="
rg -A 1 'case "' components/preview-canvas-variants.tsx | grep -E '(case|return)' | head -20
echo ""
echo "=== Checking for visible data-mode text in all variants ==="
# Search for all render/return statements with dataMode in variants file
rg -B 5 'dataMode.*live|dataMode.*sample' components/preview-canvas-variants.tsx | grep -E '(^[0-9]+-.*function|dataMode)' | head -30Repository: svg8bit/drops-studio
Length of output: 1233
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Variants that DON'T receive dataMode ==="
rg -A 8 'function EnginePreview|function CopyPreview|function CompanionPreview|function TamagotchiPreview|function HuntPreview' components/preview-canvas-variants.tsx | grep -E '(function|dataMode|{$)'
echo ""
echo "=== AggregatorPreview signature and render ==="
rg -A 15 'function AggregatorPreview' components/preview-canvas-variants.tsx | head -20
echo ""
echo "=== GamePreview signature and render ==="
rg -A 15 'function GamePreview' components/preview-canvas-variants.tsx | head -20
echo ""
echo "=== PredictionPreview signature and visible dataMode text ==="
rg -A 25 'function PredictionPreview' components/preview-canvas-variants.tsx | grep -E '(function|dataMode|return|"SAMPLE|live DropsTab)'Repository: svg8bit/drops-studio
Length of output: 1461
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== PredictionPreview full signature and body ==="
rg -A 40 'function PredictionPreview' components/preview-canvas-variants.tsx
echo ""
echo "=== Routes that pass dataMode to variants ==="
rg -B 2 'dataMode=' components/preview-canvas-variants.tsx | grep -E '(case|dataMode)'Repository: svg8bit/drops-studio
Length of output: 1574
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== All variant functions and their parameters ==="
rg 'function \w+Preview\(' components/preview-canvas-variants.tsx -A 8 | grep -E '(function|dataMode|{$)'
echo ""
echo "=== Check which variants are routed and if they get dataMode passed ==="
rg -B 1 'return \(<EnginePreview|return \(<CopyPreview|return \(<CompanionPreview|return \(<AggregatorPreview|return \(<GamePreview|return \(<PredictionPreview' components/preview-canvas-variants.tsx | head -40Repository: svg8bit/drops-studio
Length of output: 673
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== PreviewCanvasVariants routing - which variants are called and with what params ==="
rg -n 'case "' components/preview-canvas-variants.tsx | head -15
echo ""
echo "=== Detailed check: case statements with their returns ==="
sed -n '1215,1290p' components/preview-canvas-variants.tsxRepository: svg8bit/drops-studio
Length of output: 2363
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Check if EnginePreview, CopyPreview, etc. render any visible data-mode indicator ==="
rg -A 30 'function EnginePreview' components/preview-canvas-variants.tsx | head -40
echo ""
echo "=== Check SiriPreview for visible data mode ==="
rg -A 30 'function SiriPreview' components/preview-canvas-variants.tsx | head -40Repository: svg8bit/drops-studio
Length of output: 2491
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Check CopyPreview, AggregatorPreview, GamePreview render logic ==="
rg -A 25 'function CopyPreview|function AggregatorPreview|function GamePreview' components/preview-canvas-variants.tsx | grep -E '(function|return \(|className=|<span|<div|SAMPLE|live|data)'
echo ""
echo "=== Verify: does any non-dataMode variant render text about data source/mode? ==="
rg 'function \w+Preview' components/preview-canvas-variants.tsx -A 50 | grep -E '(function (Engine|Copy|Aggregator|Game|Companion|Tamagotchi|Hunt|Siri|Prediction)Preview|sample|live|DropsTab)' | head -20Repository: svg8bit/drops-studio
Length of output: 1468
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Final verification: which variants receive dataMode in the routing switch ==="
grep -n 'dataMode=' components/preview-canvas-variants.tsx | grep -E 'ChannelPreview|RadioPreview|PredictionPreview|EnginePreview|CopyPreview'
echo ""
echo "=== Count: how many case statements total? ==="
rg 'case "' components/preview-canvas-variants.tsx | wc -l
echo ""
echo "=== Confirm: EnginePreview, CopyPreview, Prediction, Game, Aggregator, Companion, Tamagotchi, Hunt, Siri do NOT receive dataMode ==="
rg -c 'dataMode' components/preview-canvas-variants.tsxRepository: svg8bit/drops-studio
Length of output: 240
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Summary: routing table with dataMode parameter ==="
sed -n '1229,1290p' components/preview-canvas-variants.tsx | grep -E '(case|<\w+Preview|dataMode)'Repository: svg8bit/drops-studio
Length of output: 974
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Confirm: EnginePreview through SiriPreview return structures (no dataMode checks) ==="
for variant in EnginePreview PredictionPreview CopyPreview AggregatorPreview GamePreview CompanionPreview TamagotchiPreview HuntPreview SiriPreview; do
echo "--- $variant ---"
rg -A 1 "function $variant" components/preview-canvas-variants.tsx | head -2
done
echo ""
echo "=== Double-check: does any of these 9 variants render visible 'sample' or 'live' text? ==="
rg 'function (Engine|Prediction|Copy|Aggregator|Game|Companion|Tamagotchi|Hunt|Siri)Preview' components/preview-canvas-variants.tsx -A 80 | grep -i 'sample\|live\|dropsTab' | head -20Repository: svg8bit/drops-studio
Length of output: 1471
Add visible data-mode labels to eight preview variants that lack them.
The section wrapper (lines 290–294) exposes the live versus sample status only through the aria-label attribute, which is not visible to sighted users. Of the nine preview variants, only ChannelPreview and RadioPreview render visible text indicating data mode. Eight variants lack any visible label: EnginePreview, PredictionPreview, CopyPreview, AggregatorPreview, GamePreview, CompanionPreview, TamagotchiPreview, and HuntPreview. Each of these variants must include an in-frame indicator such as "Sample data" or "Live data" to comply with the guideline "do not misrepresent mockups, previews, static screens, or handoffs as completed products or integrations."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/preview-canvas.tsx` around lines 290 - 294, Add visible text
indicators to the eight preview variants that currently lack data-mode labels:
EnginePreview, PredictionPreview, CopyPreview, AggregatorPreview, GamePreview,
CompanionPreview, TamagotchiPreview, and HuntPreview. For each variant, render
an in-frame label (such as "Sample data" or "Live data") conditionally based on
the dataMode state variable, similar to the existing implementation in
ChannelPreview and RadioPreview. This ensures sighted users can see whether the
preview is displaying live or sample data, matching the information already
provided in the aria-label attribute on the section wrapper.
Source: Coding guidelines
| const operations: Parameters<typeof applyProjectV2FileOperations>[2][number][] = []; | ||
|
|
||
| for (const [path, file] of Object.entries(project.files)) { | ||
| if (file.provenance === "generated" && !fresh.files[path]) { | ||
| operations.push({ type: "delete", path }); | ||
| } | ||
| } | ||
| for (const [path, file] of Object.entries(fresh.files)) { | ||
| const current = project.files[path]; | ||
| if (current && current.provenance !== "generated") continue; | ||
| if (current?.hash === file.hash) continue; | ||
| operations.push({ | ||
| type: "write", | ||
| path, | ||
| content: file.content, | ||
| language: file.language, | ||
| role: file.role, | ||
| provenance: "generated", | ||
| editable: file.editable, | ||
| }); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Read FILE_OPERATION_LIMIT and count generated template file definitions.
rg -n 'FILE_OPERATION_LIMIT\s*=' lib/project-v2-files.ts
rg -n -c '\{ path: "' lib/project-template-materializer.ts
rg -n -C3 'assertProjectV2FileSetLimits' lib/project-v2-files.tsRepository: svg8bit/drops-studio
Length of output: 984
🏁 Script executed:
# Check the materializer code at the specified lines
sed -n '339,359p' lib/project-template-materializer.ts
# Check the migration code at the specified lines
sed -n '368,375p' lib/project-v2-migration.ts
# Find and inspect the applyProjectV2FileOperations function
rg -n 'export.*function applyProjectV2FileOperations' lib/Repository: svg8bit/drops-studio
Length of output: 1191
🏁 Script executed:
# Check what assertProjectV2FileSetLimits validates
rg -n -A10 'export.*assertProjectV2FileSetLimits' lib/
# Look for validation of operation lists before calling applyProjectV2FileOperations
rg -n -B5 'applyProjectV2FileOperations' lib/project-template-materializer.ts
rg -n -B5 'applyProjectV2FileOperations' lib/project-v2-migration.tsRepository: svg8bit/drops-studio
Length of output: 1766
🏁 Script executed:
# Count actual template files more accurately
rg -n 'path: "' lib/project-template-materializer.ts | wc -l
# Also check the actual template structure
ast-grep outline lib/project-template-materializer.tsRepository: svg8bit/drops-studio
Length of output: 785
🏁 Script executed:
# Get the complete applyProjectV2FileOperations function
sed -n '139,250p' lib/project-v2-files.tsRepository: svg8bit/drops-studio
Length of output: 4179
🏁 Script executed:
# Check what both limits are and their relationship
rg -n 'PROJECT_V2_FILE_LIMIT\s*=' lib/
rg -n 'FILE_OPERATION_LIMIT\s*=' lib/
# Check if operations are validated against the limit
rg -n -A5 'operations.*length' lib/project-v2-files.ts | head -30Repository: svg8bit/drops-studio
Length of output: 639
🏁 Script executed:
# Check how many files a project can realistically have
rg -n 'PROJECT_V2_FILE_LIMIT' lib/ -A2 -B2
# Check if there's any batching logic around applyProjectV2FileOperations calls
rg -n -B10 -A5 'applyProjectV2FileOperations' lib/project-template-materializer.ts | grep -A15 'const operations'
# Check the preserved operations building in migration
sed -n '340,380p' lib/project-v2-migration.tsRepository: svg8bit/drops-studio
Length of output: 3914
🏁 Script executed:
# Verify the actual structure of what gets passed - is it really unbounded?
ast-grep outline lib/project-v2-migration.tsRepository: svg8bit/drops-studio
Length of output: 1239
🏁 Script executed:
# Check the actual generated files from the template
sed -n '43,180p' lib/project-template-materializer.ts | head -60Repository: svg8bit/drops-studio
Length of output: 3371
🏁 Script executed:
# Count all the files that the template generates across all categories
rg -n '"' lib/project-template-materializer.ts | grep -E 'path: |tasks\[' | wc -l
# Check what files are actually in the categories object
ast-grep run --pattern 'categories = {
$$$
}' --lang typescript lib/project-template-materializer.tsRepository: svg8bit/drops-studio
Length of output: 161
🏁 Script executed:
# Check the sourceFiles function to see what files are actually generated
sed -n '195,240p' lib/project-template-materializer.tsRepository: svg8bit/drops-studio
Length of output: 7631
🏁 Script executed:
# Check materializeProjectV2Template to see what files are created
sed -n '243,330p' lib/project-template-materializer.tsRepository: svg8bit/drops-studio
Length of output: 3313
🏁 Script executed:
# Check how many files can be in a project and verify the actual maximum operations needed
python3 - <<'PY'
# Template generates 23 files
template_files = 23
# Maximum project files per limit
max_project_files = 64
# FILE_OPERATION_LIMIT
file_op_limit = 64
# Scenario 1: refreshGeneratedProjectV2Template
# If project has all 64 files (64 max), with 50 generated and 14 manual
# Template has 23 files. Worst case: delete all 50 old generated, write 23 new = 73 operations (EXCEEDS!)
scenario1_deletes = 50
scenario1_writes = 23
scenario1_ops = scenario1_deletes + scenario1_writes
print(f"Scenario 1 (refresh template): {scenario1_ops} operations (limit: {file_op_limit})")
print(f" - Exceeds limit: {scenario1_ops > file_op_limit}")
# Scenario 2: refreshLegacyProjectV2Migration
# If project has 64 files total, with 60 non-generated (preserved)
scenario2_preserved = 60
print(f"\nScenario 2 (legacy migration): {scenario2_preserved} preserved operations (limit: {file_op_limit})")
print(f" - Exceeds limit: {scenario2_preserved > file_op_limit}")
# Scenario 3: refreshLegacyProjectV2Migration
# If project has 64 files total, with 64 non-generated (all preserved)
scenario3_preserved = 64
print(f"\nScenario 3 (all preserved): {scenario3_preserved} preserved operations (limit: {file_op_limit})")
print(f" - Exceeds limit: {scenario3_preserved > file_op_limit}")
print(f" - At boundary: {scenario3_preserved == file_op_limit}")
PYRepository: svg8bit/drops-studio
Length of output: 444
🏁 Script executed:
# Verify the validation is in place in applyProjectV2FileOperations
grep -n "operations.length > FILE_OPERATION_LIMIT" lib/project-v2-files.ts
# Check if there's any pre-validation before calling applyProjectV2FileOperations in either adapter
sed -n '330,385p' lib/project-template-materializer.ts | grep -E 'operations\.length|assert'
sed -n '340,380p' lib/project-v2-migration.ts | grep -E 'preservedOperations\.length|assert'Repository: svg8bit/drops-studio
Length of output: 374
🏁 Script executed:
# Verify that scenario 1 is realistic - check if there can actually be 50+ generated files before refresh
# Look at what a worst-case spec change might delete/regenerate
rg -n 'managed\.' lib/project-template-materializer.ts | head -20Repository: svg8bit/drops-studio
Length of output: 1187
🏁 Script executed:
# Check if there's any batching or chunking logic elsewhere
rg -n 'batch\|chunk' lib/ | grep -i 'operation\|file'Repository: svg8bit/drops-studio
Length of output: 158
Both refresh adapters can exceed FILE_OPERATION_LIMIT when building unbounded operation lists. refreshGeneratedProjectV2Template constructs delete and write operations for the full file set without pre-validation; a project with 50+ existing generated files requires 50 deletes plus 23 template writes, totaling 73 operations and exceeding the 64-operation limit. refreshLegacyProjectV2Migration preserves all non-generated files as write operations and reaches the limit at exactly 64 preserved files. Both adapters pass unbounded lists to applyProjectV2FileOperations, which validates and rejects them at runtime.
Batch or bound the operation lists before passing them to applyProjectV2FileOperations:
lib/project-template-materializer.ts#L339-L359: Pre-validateoperations.lengthagainstFILE_OPERATION_LIMITbefore line 379, or split operations into batches if necessary.lib/project-v2-migration.ts#L356-L375: Pre-validatepreservedOperations.lengthagainstFILE_OPERATION_LIMITbefore line 368, or batch the preserved operations.
📍 Affects 2 files
lib/project-template-materializer.ts#L339-L359(this comment)lib/project-v2-migration.ts#L368-L375
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/project-template-materializer.ts` around lines 339 - 359, The refresh
adapters build operation arrays without enforcing FILE_OPERATION_LIMIT before
calling applyProjectV2FileOperations. In lib/project-template-materializer.ts
lines 339-359, update refreshGeneratedProjectV2Template to validate or batch
operations before the applyProjectV2FileOperations call; in
lib/project-v2-migration.ts lines 368-375, apply the same limit handling to
preservedOperations in refreshLegacyProjectV2Migration. Ensure neither adapter
passes more than FILE_OPERATION_LIMIT operations in a single call.
| next = { | ||
| ...next, | ||
| manifest: fresh.manifest, | ||
| productSpec: fresh.productSpec, | ||
| integrations: fresh.integrations, | ||
| environment: fresh.environment, | ||
| permissions: fresh.permissions, | ||
| tasks: fresh.tasks, | ||
| updatedAt: now, | ||
| contentHash: "", | ||
| }; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Manifest can desync from a preserved manual package.json.
The loop at lines 346-359 skips paths whose provenance is not generated, so a manually edited package.json stays in files. Line 399 then replaces manifest with fresh.manifest, which is derived from the deterministic template package manifest. After the refresh, manifest.dependencies and manifest.scripts can disagree with the retained package.json content. applyProjectV2FileOperations normally keeps these in sync through syncManifestFromPackage. Re-sync the manifest from the resulting package.json after the metadata merge, or exclude package.json fields from the fresh manifest overwrite.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/project-template-materializer.ts` around lines 397 - 407, The metadata
merge around next.manifest must preserve consistency with a retained manually
edited package.json. After applying the fresh metadata in this refresh flow,
re-synchronize manifest from the resulting package.json using the existing
syncManifestFromPackage behavior, or avoid overwriting package.json-derived
manifest fields while merging fresh.manifest.
| const togglePlayback = () => { | ||
| if (typeof window === "undefined" || !("speechSynthesis" in window)) { | ||
| setPlayback("Browser speech is unavailable"); | ||
| return; | ||
| } | ||
| if (playing) { | ||
| window.speechSynthesis.cancel(); | ||
| setPlaying(false); | ||
| setPlayback("Playback stopped"); | ||
| return; | ||
| } | ||
| const utterance = new SpeechSynthesisUtterance(script); | ||
| utterance.rate = 0.96; | ||
| utterance.pitch = 0.92; | ||
| utterance.volume = volume / 100; | ||
| utterance.onend = () => { setPlaying(false); setPlayback("Segment complete"); }; | ||
| utterance.onerror = () => { setPlaying(false); setPlayback("Playback could not start"); }; | ||
| window.speechSynthesis.cancel(); | ||
| window.speechSynthesis.speak(utterance); | ||
| setPlaying(true); | ||
| setPlayback("Browser speech active"); | ||
| }; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Stop browser speech when the generated component unmounts.
togglePlayback starts a SpeechSynthesisUtterance through the global speechSynthesis queue. The generated component never cancels it on unmount, so the briefing keeps playing after the user navigates away inside the generated app. Add a cleanup effect. The template import list needs useEffect.
🔧 Proposed cleanup
-import { createContext, useContext, useState, type ReactNode } from "react";
+import { createContext, useContext, useEffect, useState, type ReactNode } from "react"; const [volume, setVolume] = useState(72);
+ useEffect(() => () => { window.speechSynthesis?.cancel(); }, []);
const currentSegment = queue[segmentIndex] ?? queue[0];📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const togglePlayback = () => { | |
| if (typeof window === "undefined" || !("speechSynthesis" in window)) { | |
| setPlayback("Browser speech is unavailable"); | |
| return; | |
| } | |
| if (playing) { | |
| window.speechSynthesis.cancel(); | |
| setPlaying(false); | |
| setPlayback("Playback stopped"); | |
| return; | |
| } | |
| const utterance = new SpeechSynthesisUtterance(script); | |
| utterance.rate = 0.96; | |
| utterance.pitch = 0.92; | |
| utterance.volume = volume / 100; | |
| utterance.onend = () => { setPlaying(false); setPlayback("Segment complete"); }; | |
| utterance.onerror = () => { setPlaying(false); setPlayback("Playback could not start"); }; | |
| window.speechSynthesis.cancel(); | |
| window.speechSynthesis.speak(utterance); | |
| setPlaying(true); | |
| setPlayback("Browser speech active"); | |
| }; | |
| import { createContext, useContext, useEffect, useState, type ReactNode } from "react"; | |
| const [volume, setVolume] = useState(72); | |
| useEffect(() => () => { window.speechSynthesis?.cancel(); }, []); | |
| const currentSegment = queue[segmentIndex] ?? queue[0]; | |
| const togglePlayback = () => { | |
| if (typeof window === "undefined" || !("speechSynthesis" in window)) { | |
| setPlayback("Browser speech is unavailable"); | |
| return; | |
| } | |
| if (playing) { | |
| window.speechSynthesis.cancel(); | |
| setPlaying(false); | |
| setPlayback("Playback stopped"); | |
| return; | |
| } | |
| const utterance = new SpeechSynthesisUtterance(script); | |
| utterance.rate = 0.96; | |
| utterance.pitch = 0.92; | |
| utterance.volume = volume / 100; | |
| utterance.onend = () => { setPlaying(false); setPlayback("Segment complete"); }; | |
| utterance.onerror = () => { setPlaying(false); setPlayback("Playback could not start"); }; | |
| window.speechSynthesis.cancel(); | |
| window.speechSynthesis.speak(utterance); | |
| setPlaying(true); | |
| setPlayback("Browser speech active"); | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/project-template-ui.ts` around lines 181 - 202, Import useEffect in the
generated component template and add an unmount cleanup effect that calls
window.speechSynthesis.cancel() when browser speech is available. Place the
cleanup alongside togglePlayback so any queued or active utterance stops when
the component unmounts, while preserving the existing playback behavior.
| .radio-player-top strong { color: var(--project-accent); font-size: 12px; letter-spacing: .12em; } | ||
| .radio-track-label { margin: 34px 0 12px; color: #6f7d82; font-size: 12px; font-weight: 800; letter-spacing: .12em; } | ||
| .radio-player h2 { margin: 0; font-size: clamp(2.3rem, 4.8vw, 4.6rem); line-height: .92; letter-spacing: -.055em; } | ||
| .radio-player > p:not(.radio-track-label, .radio-playback) { min-height: 74px; color: #a9b4b8; font-size: 14px; line-height: 1.65; } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Raise generated body copy to the required size.
.radio-player > p:not(.radio-track-label, .radio-playback) sets the segment script text to 14 px. .radio-archive p sets the archive description text to 13 px. Both are body copy in the generated product. The design rules require 16–18 px for body text; 12 px is reserved for helper and metadata text.
🎨 Proposed size fix
-.radio-player > p:not(.radio-track-label, .radio-playback) { min-height: 74px; color: `#a9b4b8`; font-size: 14px; line-height: 1.65; }
+.radio-player > p:not(.radio-track-label, .radio-playback) { min-height: 74px; color: `#a9b4b8`; font-size: 16px; line-height: 1.65; }
@@
-.radio-archive p { margin: 0; color: `#7d8a8f`; font-size: 13px; line-height: 1.55; }
+.radio-archive p { margin: 0; color: `#7d8a8f`; font-size: 16px; line-height: 1.55; }As per coding guidelines: "Body text must be 16–18 px, control text at least 14 px, helper and metadata text at least 12 px".
Also applies to: 443-443
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/project-template-ui.ts` at line 396, Update the generated body-copy
styles in the radio player and archive sections: change the `.radio-player >
p:not(.radio-track-label, .radio-playback)` and `.radio-archive p` font sizes to
comply with the required 16–18px body-text range, while leaving helper and
metadata styles unchanged.
Source: Coding guidelines
| const next: ProjectV2 = { | ||
| ...refreshed, | ||
| revision, | ||
| integrations: current.integrations, | ||
| environment: current.environment, | ||
| permissions: current.permissions, | ||
| deployment: current.deployment, | ||
| migration: current.migration, | ||
| runs: current.runs, | ||
| logs: current.logs, | ||
| checkpoints: current.checkpoints, | ||
| preview: current.preview | ||
| ? { | ||
| status: "stopped", | ||
| projectRevision: revision, | ||
| stoppedAt: updatedAt, | ||
| } | ||
| : undefined, | ||
| createdAt: current.createdAt, | ||
| updatedAt, | ||
| contentHash: "", | ||
| }; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Preserved runs can reference tasks that the refreshed baseline no longer defines.
next keeps current.runs, current.logs, and current.checkpoints, but tasks come from refreshed, which is derived from migrateGeneratedProjectToV2. That function selects the task set from the workspace state (workspaceTasks(workspace)) or from the HTML-only default list. If the incoming generatedProject no longer produces a task id that an existing run references, validateProjectV2 throws Project run <id> references an unknown task. and the refresh fails. The caller in components/project-studio.tsx then blocks the save and shows the refresh error. Preserve current.tasks, or drop runs and logs whose task no longer exists.
🐛 Proposed fix
const next: ProjectV2 = {
...refreshed,
revision,
+ tasks: current.tasks,
integrations: current.integrations,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const next: ProjectV2 = { | |
| ...refreshed, | |
| revision, | |
| integrations: current.integrations, | |
| environment: current.environment, | |
| permissions: current.permissions, | |
| deployment: current.deployment, | |
| migration: current.migration, | |
| runs: current.runs, | |
| logs: current.logs, | |
| checkpoints: current.checkpoints, | |
| preview: current.preview | |
| ? { | |
| status: "stopped", | |
| projectRevision: revision, | |
| stoppedAt: updatedAt, | |
| } | |
| : undefined, | |
| createdAt: current.createdAt, | |
| updatedAt, | |
| contentHash: "", | |
| }; | |
| const next: ProjectV2 = { | |
| ...refreshed, | |
| revision, | |
| tasks: current.tasks, | |
| integrations: current.integrations, | |
| environment: current.environment, | |
| permissions: current.permissions, | |
| deployment: current.deployment, | |
| migration: current.migration, | |
| runs: current.runs, | |
| logs: current.logs, | |
| checkpoints: current.checkpoints, | |
| preview: current.preview | |
| ? { | |
| status: "stopped", | |
| projectRevision: revision, | |
| stoppedAt: updatedAt, | |
| } | |
| : undefined, | |
| createdAt: current.createdAt, | |
| updatedAt, | |
| contentHash: "", | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/project-v2-migration.ts` around lines 378 - 399, Update the ProjectV2
construction in the migration refresh flow to keep task references consistent
with preserved runs, logs, and checkpoints. Preserve current.tasks alongside the
existing current state fields, or filter out persisted runs and related logs
whose task IDs are absent from refreshed.tasks before validation; ensure
validateProjectV2 cannot encounter unknown task references.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 34b37ced7a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| spec: GeneratedProjectSpec; | ||
| }; | ||
|
|
||
| const PROJECT_V2_BUILD_TASKS = ["typecheck", "lint", "test", "build"] as const; |
There was a problem hiding this comment.
Verify the tasks declared by migrated projects
For every migrated legacy-html project, this hard-coded list requires successful typecheck and lint runs, but migrateGeneratedProjectToV2() declares only check/test/build/start for workspace projects (and test/build/dev for HTML-only projects). Even after every declared task and the preview succeed, these preserved V1 projects can reach at most 3/5, so Studio permanently reports “Build pending” instead of a verified release; derive required checks from the project's task kinds or handle the legacy adapter separately.
AGENTS.md reference: AGENTS.md:L76-L76
Useful? React with 👍 / 👎.
| const browserTelemetryReady = Boolean( | ||
| runtimeSmoke?.mode === "browser" | ||
| runtimePreviewUrl | ||
| || ( |
There was a problem hiding this comment.
Wait for actual browser telemetry before claiming it
When a current-revision Sandbox preview merely has a ready HTTPS URL, this branch immediately sets data-runtime-ready="true" and, on the mobile layout, displays “Browser telemetry” before the iframe has loaded or emitted any runtime evidence. An expired, blocked, or failing preview therefore presents a verified telemetry state based only on stored preview metadata; keep URL availability separate from the existing executed browser-smoke condition.
AGENTS.md reference: AGENTS.md:L111-L111
Useful? React with 👍 / 👎.
| .device-switch button.active { box-shadow: 0 2px 5px rgba(28,46,75,.1); } | ||
| .runtime-browser { background: white; border: 1px solid #d4dfec; border-radius: 14px; box-shadow: 0 14px 36px rgba(28,48,80,.1); height: calc(100% - 46px); margin: auto; min-height: 590px; overflow: auto; transition: .2s ease; width: 100%; } | ||
| .canvas-zoom-controls { align-items: center; background: #e3e9f2; border-radius: 8px; display: inline-flex; flex: 0 0 auto; padding: 2px; } | ||
| .canvas-zoom-controls button { justify-content: center; min-width: 38px; padding-inline: 7px; } |
There was a problem hiding this comment.
Make the zoom controls at least 44 pixels wide
The newly visible zoom-out and zoom-in buttons compute to only 38 CSS pixels wide (their SVG and padding do not exceed this minimum), so their pointer targets are 38×44 even though the later workspace rule supplies a 44px height. Increase the minimum width to 44px so these frequently used canvas controls meet the repository's interaction-target requirement.
AGENTS.md reference: AGENTS.md:L28-L28
Useful? React with 👍 / 👎.
|
|
||
| next = { | ||
| ...next, | ||
| manifest: fresh.manifest, |
There was a problem hiding this comment.
Preserve an edited package manifest during spec refresh
When Builder has changed package.json—for example through the supported install_package tool—the file has AI provenance and is deliberately preserved earlier in this function, but this assignment replaces the synchronized manifest with the fresh template's dependency and script records. The final validateProjectV2() then rejects the revision because the manifest no longer matches package.json, so any later Director/spec edit cannot be saved; retain the manifest derived from the preserved package file while updating only spec-owned fields.
AGENTS.md reference: AGENTS.md:L105-L105
Useful? React with 👍 / 👎.
| window.open( | ||
| "/?connections=1", | ||
| "_blank", | ||
| "noopener,noreferrer", |
There was a problem hiding this comment.
Keep connection setup in the originating tab
Both connection shortcuts now open a new tab, but model and DropsTab credentials are written exclusively to sessionStorage. The child tab receives an initial copy of the opener's storage, yet subsequent connection writes do not propagate back, and noopener prevents any explicit handoff, so returning to the original project leaves its provider/key state unchanged and the new connection unusable there; retain same-tab navigation or implement a secure result handoff.
AGENTS.md reference: AGENTS.md:L76-L76
Useful? React with 👍 / 👎.
| .radio-player-top strong { color: var(--project-accent); font-size: 12px; letter-spacing: .12em; } | ||
| .radio-track-label { margin: 34px 0 12px; color: #6f7d82; font-size: 12px; font-weight: 800; letter-spacing: .12em; } | ||
| .radio-player h2 { margin: 0; font-size: clamp(2.3rem, 4.8vw, 4.6rem); line-height: .92; letter-spacing: -.055em; } | ||
| .radio-player > p:not(.radio-track-label, .radio-playback) { min-height: 74px; color: #a9b4b8; font-size: 14px; line-height: 1.65; } |
There was a problem hiding this comment.
Raise radio script copy to the body-text minimum
The generated Crypto Radio player's segment script is normal paragraph body copy, but this new rule renders it at 14px across every viewport. That makes the primary content users read and edit smaller than the repository's mandatory 16–18px body scale; set this paragraph to at least 16px while leaving true metadata at 12px.
AGENTS.md reference: AGENTS.md:L24-L24
Useful? React with 👍 / 👎.
| .slice(0, PROJECT_STORE_LIMIT - 1); | ||
| const merged = [project, ...retainedExisting] | ||
| .sort((left, right) => timestamp(right) - timestamp(left)); | ||
| const compatibilityIndex = merged.map(compactProjectForCompatibilityIndex); |
There was a problem hiding this comment.
Preserve V2 snapshots for every retained project
When the legacy array contains multiple local projects with projectV2 snapshots, saving any one project compacts every retained entry here, but the subsequent per-project write creates an item record only for the project being saved. Retained projects that do not already have item records therefore lose their canonical V2 files, runs, and checkpoints on the next read or reload; migrate all retained full snapshots to item records before stripping them from the compatibility index, or fail without discarding them.
AGENTS.md reference: AGENTS.md:L105-L105
Useful? React with 👍 / 👎.
Ships the final Drops Studio production hotfix: unified branding, responsive workspace, v0-like canvas controls, official integration marks, stable Connections navigation, honest capability states, Project V2 persistence repair, legacy adapter synchronization, and retained design QA evidence.
Verified: UI guardrails, ESLint, TypeScript, 760 unit passes with 2 opt-in skips, Next/Vercel build, Vinext/Cloudflare build, Storybook build plus 47 tests, focused Playwright at 1440/1024/390, three Lighthouse runs, npm audit, and CodeRabbit review. Visual baselines were not rewritten.
Summary by CodeRabbit
New Features
Bug Fixes
Style