Repository navigation
feat: add agent-authored guides and simplify the dashboard - #107
Conversation
|
Updates to Preview Branch (feat/finish-captuto-mvp) ↗︎
Tasks are run on every commit but only new migration files are pushed.
View logs for this Workflow Run ↗︎. |
There was a problem hiding this comment.
5 issues found across 42 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/web/components/editor/EditorClient.tsx">
<violation number="1" location="apps/web/components/editor/EditorClient.tsx:71">
P2: Title-save failures set `saveStatus` to `error`, but successful title saves never reset it, so the editor can show a stale error state.</violation>
<violation number="2" location="apps/web/components/editor/EditorClient.tsx:249">
P2: New operation-specific errors can become stale because success paths for those operations don’t clear `editorError`.</violation>
</file>
<file name="apps/web/app/login/page.tsx">
<violation number="1" location="apps/web/app/login/page.tsx:26">
P2: `Promise.race` with a timeout can misclassify slow authenticated sessions as logged out, leaving signed-in users on the login page.</violation>
</file>
<file name="apps/web/app/dashboard/page.tsx">
<violation number="1" location="apps/web/app/dashboard/page.tsx:64">
P2: Using `mutateAsync` here introduces an unhandled rejection path on delete failures. Use `mutate` (or catch the promise) so errors are handled via mutation state without bubbling a rejected promise from the click handler.</violation>
</file>
<file name="apps/web/app/api/oembed/route.ts">
<violation number="1" location="apps/web/app/api/oembed/route.ts:57">
P2: Using expiring signed screenshot URLs in `thumbnail_url` can produce broken oEmbed thumbnails after the 7-day signature expires.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
| console.error('Failed to update title:', error); | ||
| // Rollback on error | ||
| setEditorError('The title could not be saved. Your previous title was restored.'); | ||
| setSaveStatus('error'); |
There was a problem hiding this comment.
P2: Title-save failures set saveStatus to error, but successful title saves never reset it, so the editor can show a stale error state.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/components/editor/EditorClient.tsx, line 71:
<comment>Title-save failures set `saveStatus` to `error`, but successful title saves never reset it, so the editor can show a stale error state.</comment>
<file context>
@@ -63,9 +64,11 @@ export function EditorClient({
console.error('Failed to update title:', error);
- // Rollback on error
+ setEditorError('The title could not be saved. Your previous title was restored.');
+ setSaveStatus('error');
setTutorial((prev) => ({ ...prev, title: previousTitle }));
}
</file context>
| } | ||
| } catch (error) { | ||
| console.error('Failed to reorder steps:', error); | ||
| setEditorError('The step order could not be saved. The previous order was restored.'); |
There was a problem hiding this comment.
P2: New operation-specific errors can become stale because success paths for those operations don’t clear editorError.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/components/editor/EditorClient.tsx, line 249:
<comment>New operation-specific errors can become stale because success paths for those operations don’t clear `editorError`.</comment>
<file context>
@@ -241,6 +246,7 @@ export function EditorClient({
}
} catch (error) {
console.error('Failed to reorder steps:', error);
+ setEditorError('The step order could not be saved. The previous order was restored.');
setSteps(previousSteps);
} finally {
</file context>
| const checkAuth = async () => { | ||
| try { | ||
| const supabase = createClient(); | ||
| const authResult = await Promise.race([ |
There was a problem hiding this comment.
P2: Promise.race with a timeout can misclassify slow authenticated sessions as logged out, leaving signed-in users on the login page.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/login/page.tsx, line 26:
<comment>`Promise.race` with a timeout can misclassify slow authenticated sessions as logged out, leaving signed-in users on the login page.</comment>
<file context>
@@ -16,18 +18,37 @@ export default function LoginPage() {
+ const checkAuth = async () => {
+ try {
+ const supabase = createClient();
+ const authResult = await Promise.race([
+ supabase.auth.getUser(),
+ new Promise<null>((resolve) =>
</file context>
| const handleDelete = async (tutorialId: string) => { | ||
| await deleteMutation.mutateAsync(tutorialId); |
There was a problem hiding this comment.
P2: Using mutateAsync here introduces an unhandled rejection path on delete failures. Use mutate (or catch the promise) so errors are handled via mutation state without bubbling a rejected promise from the click handler.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/dashboard/page.tsx, line 64:
<comment>Using `mutateAsync` here introduces an unhandled rejection path on delete failures. Use `mutate` (or catch the promise) so errors are handled via mutation state without bubbling a rejected promise from the click handler.</comment>
<file context>
@@ -49,22 +51,18 @@ export default function DashboardPage() {
- const handleDelete = (tutorialId: string) => {
- deleteMutation.mutate(tutorialId);
+ const handleDelete = async (tutorialId: string) => {
+ await deleteMutation.mutateAsync(tutorialId);
};
</file context>
| const handleDelete = async (tutorialId: string) => { | |
| await deleteMutation.mutateAsync(tutorialId); | |
| const handleDelete = (tutorialId: string) => { | |
| deleteMutation.mutate(tutorialId); |
| const thumbnailUrl = | ||
| previewImageUrl || | ||
| `${baseUrl}/api/og/tutorial?title=${encodeURIComponent(tutorial.title)}&steps=${steps.length}`; |
There was a problem hiding this comment.
P2: Using expiring signed screenshot URLs in thumbnail_url can produce broken oEmbed thumbnails after the 7-day signature expires.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/api/oembed/route.ts, line 57:
<comment>Using expiring signed screenshot URLs in `thumbnail_url` can produce broken oEmbed thumbnails after the 7-day signature expires.</comment>
<file context>
@@ -36,45 +36,45 @@ export async function GET(request: NextRequest) {
- const embedToken = tutorial.public_token;
+ const embedToken = tutorial.publicToken;
const embedUrl = `${baseUrl}/t/${embedToken}/embed`;
+ const thumbnailUrl =
+ previewImageUrl ||
+ `${baseUrl}/api/og/tutorial?title=${encodeURIComponent(tutorial.title)}&steps=${steps.length}`;
</file context>
| const thumbnailUrl = | |
| previewImageUrl || | |
| `${baseUrl}/api/og/tutorial?title=${encodeURIComponent(tutorial.title)}&steps=${steps.length}`; | |
| const thumbnailUrl = `${baseUrl}/api/og/tutorial?title=${encodeURIComponent(tutorial.title)}&steps=${steps.length}`; |
Summary
Captuto can turn a human Mac recording into an agent-authored guide. The recorder offers browser sign-in when no credential is configured, then retrieves its own token automatically. A standard HTTP MCP exposes captures, narration/transcription, step editing, annotations, rendered previews, sharing and PDF export; it does not control the recorded application.
The authenticated homepage now has working search, sorting and status filters, 25-guide server pagination, clear errors and real edit/share/PDF actions. Placeholder analytics, notifications, folders and browser recording controls are removed. A local 1,001-guide workload reduced initial JSON from 221,180 to 5,138 bytes (97.7%); warm API latency was similar (64 vs 61 ms). Initial dashboard JavaScript decreased from 152 to 147 kB. Details and reproducible checks:
docs/captuto/dashboard-performance.md.This branch integrates the current Studio editor from main, adds rectangle tools and reliable save errors, and refreshes the public guide and PDF layout. It also retains the existing billing work from this PR. The web redesign is partial; this is not a claim that the entire product has been redesigned or launch-tested.
Verification
Independent
review-code-devdeep review covered 160 files with frontend, auth, API, export-privacy and migration lenses. Its two findings were fixed and rechecked: idempotent Stripe checkout and bounded pairing admission. The dashboard delta received a targeted follow-up review; its 320px header overflow was fixed and rechecked. No issues remain in the review gate. Local checks pass (365 web tests, 71 extension tests, 21 Swift tests); all five executed latest-SHA checks pass. GitHub also retains eleven external-app suites in queued state with zero runs (also present on the previous commit); strict suite-level readiness awaits those integrations, so this is not an all-suites-green claim.Release notes
Configure server-side
SUPABASE_SERVICE_ROLE_KEYbefore code/migration rollout. Public policy removal requires the matching server routes; deploy through the existing CI/CD. Recovery/deploy-order notes:docs/captuto/development.md. Deepgram is required for transcription. Existing Stripe billing needsSTRIPE_SECRET_KEY,STRIPE_PRICE_ID, andSTRIPE_WEBHOOK_SECREToutside Git.A long real-world manual recording and performance acceptance are still product validation work. Audio uploads remain subject to hosting request-size limits. No manual deployment or merge is part of this PR preparation.
Screenshots
Landing
Dashboard
Mobile dashboard
Settings and MCP
Studio editor
Guide preview
Public guide
Mobile guide
PDF export
Mac recorder
Mac connection
Connected recorder
Browser authorization
No Linear issue: work was explicitly authorized without a ticket.