Skip to content

Commit c8f28b8

Browse files
[codex] stabilize openrouter defaults and app identity (#197)
* fix public icon pack editing * fix public icon library editing * fix diagram agent reliability * stabilize openrouter defaults and app identity
1 parent b050136 commit c8f28b8

30 files changed

Lines changed: 825 additions & 1573 deletions

.env.e2e.example

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
# Required for LLM/vision tests (set in GitHub Actions secrets)
44
OPENROUTER_API_KEY=sk-or-v1-your-key-here
5-
MODEL_NAME=google/gemini-2.5-flash-lite
5+
MODEL_NAME=google/gemini-3-flash-preview
66
VISION_MODEL_NAME=google/gemini-3-flash-preview
7+
SKETCHI_APP_NAME=sketchi
8+
SKETCHI_APP_COMPONENT=backend
79
AI_GATEWAY_API_KEY=vck_your-key-here
810

911
# Optional (only needed for Browserbase rendering + Stagehand BROWSERBASE env)
@@ -19,6 +21,10 @@ STAGEHAND_TARGET_URL=http://localhost:3001
1921
# Environment: LOCAL or BROWSERBASE (defaults to BROWSERBASE)
2022
STAGEHAND_ENV=LOCAL
2123

24+
# Optional authenticated local WorkOS test user
25+
SKETCHI_E2E_EMAIL=your-workos-test-user@maildrop.cc
26+
SKETCHI_E2E_PASSWORD=your-workos-test-password
27+
2228
# Browser settings
2329
STAGEHAND_HEADLESS=false
2430
STAGEHAND_CHROME_PATH=

.ignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Re-include agent working state for OpenCode indexing (ripgrep)
22
# These are gitignored but agents need to search/read them
33
!.sisyphus/
4+
!.sisyphus/**
45
!.agents/skills
5-
!.memory/
6+
!.agents/skills/**
7+
!.memory/
8+
!.memory/**

AGENTS.md

Lines changed: 18 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,20 @@
1-
## Repo CI / Deploy Note
2-
- Vercel Preview/Prod: `NEXT_PUBLIC_CONVEX_URL` is set automatically by the Convex deploy step. If it's undefined, the Convex deploy is failing (debug that first).
3-
- Pre-push sanity: `bun x ultracite fix`, `bun x ultracite check`, `bun run check-types`, `bun run build`, and `cd packages/backend && bunx convex codegen && bun run test`.
4-
- don't create new branches unless asked
5-
---
6-
7-
# Working Preferences
8-
9-
## Communication
10-
- succinct; no filler; fragments OK
11-
- facts first; show evidence (commands + exit codes)
12-
13-
## Engineering
14-
- ship small, testable increments
15-
- readable > clever
16-
- skimmable code; split files at ~400 lines
17-
- descriptive names; long OK
18-
- docstrings: skip or "why" only
19-
- latest packages; `bun install`
20-
21-
## Languages
22-
- TypeScript first
23-
- Go OK for utilities
24-
- avoid Python
25-
26-
## Workflow
27-
- commit + push often (small commits)
28-
- delete obsolete docs/experiments
29-
- use `gh` for GitHub issues/PRs instead of web UI
30-
31-
## Linting
32-
- `bun x ultracite fix` format
33-
- `bun x ultracite check` verify
34-
35-
## Repo
36-
- Turborepo + bun workspaces
37-
- `apps/web/` Next.js frontend
38-
- `packages/backend/` Convex functions
39-
- `bun run dev` all apps
40-
- `bun run build` builds
41-
42-
## Testing
43-
Priority: API > E2E > manual/verification > unit (last resort)
44-
45-
1. **API tests** - for true public APIs (none currently)
46-
2. **E2E tests** - primary method; UI flows
47-
3. **Manual Verification** - for fixes where CI tests add low value; use checklist + log analysis
48-
49-
Never mock HTTP; `convex-test` mocks Convex backend only. Verify functional intent/behavior over code coverage.
50-
51-
## Test Planning
52-
Outline scenarios upfront. Create `.ts` (for E2E) or a manual checklist (for verification) with description comment - confirm approach before implementing.
53-
54-
For manual verification, structure the checklist and log requirements so they could be automated via an LLM (e.g. "analyze logs for X abnormality"). Add results/logs to issue comments.
55-
56-
## Stagehand E2E
57-
Location: `tests/e2e/` | Stagehand 3 TS via OpenRouter
58-
59-
Models:
60-
- `google/gemini-2.5-flash-lite` general
61-
- `google/gemini-3-flash-preview` complex
62-
63-
Guidelines:
64-
- prompt-first; avoid brittle selectors
65-
- start dev server locally
66-
- `STAGEHAND_TARGET_URL` for preview regression
67-
68-
## Adding Tests
69-
1. Create `.ts` in `tests/e2e/`
70-
2. Add scenario comment at top
71-
3. Confirm approach first
72-
4. Run locally before commit; preview after deploy
1+
## Repo Constraints
2+
- **Branches**: only one active branch other than main at a time (cleanup or recommend cleanup if found in violation)
3+
- **Vercel**: `NEXT_PUBLIC_CONVEX_URL` is automatic. If undefined, Convex deploy failed.
4+
- **Pre-push**: `bun x ultracite fix`, `bun run check-types`, `bun run build`, and `cd packages/backend && bun run test`.
5+
6+
## Preferences
7+
- **Communication**: Succinct; fragments OK; facts first; show evidence (commands + exit codes).
8+
- **Engineering**: `readable > clever`; long descriptive names OK; split files at ~400 lines.
9+
10+
## Testing Strategy
11+
- **Priority**: API > E2E > manual/verification.
12+
- **API (Convex)**: `packages/backend/convex/*.test.ts`. Never mock HTTP; verify functional intent.
13+
- **E2E (Stagehand)**: Prompt-first selectors; avoid brittle CSS. Use `STAGEHAND_TARGET_URL` for previews.
14+
- **Authenticated local E2E**: When a flow requires WorkOS sign-in, use `SKETCHI_E2E_EMAIL` and `SKETCHI_E2E_PASSWORD` from local env files instead of ad hoc credentials.
15+
- **Local auth/editor overrides**: For local WorkOS + Convex verification, prefer `SKETCHI_ADMIN_SUBJECTS` / `SKETCHI_ICON_LIBRARY_EDITOR_SUBJECTS` in addition to email allowlists. Local Convex identities may not include email claims even when the user is signed in.
16+
- **UI verification**: For any UI/E2E-affecting change, run a targeted local verification against the real app before finishing. Prefer `agent-browser` for the interaction path and `d3k` for browser/server log review; at minimum run the real dev server with `bun run dev` and verify the affected flow there.
17+
- **Manual**: Checklist + log analysis (`venom.log` or Convex logs).
7318

7419
## Memory
75-
- Use .memory/ directory to store any temporary artifacts.
76-
- This directory is gitignored, so it will not be committed to the repository, but it is intentionally configured to be visible to codex.
20+
- Use `.memory/` for temporary artifacts (gitignored but visible to tools).

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
- [What is Sketchi?](#what-is-sketchi)
8585
- [Screenshots](#screenshots)
8686
- [Architecture](#architecture)
87+
- [Model Strategy](#model-strategy)
8788
- [Features](#features)
8889
- [Quick Start](#quick-start)
8990
- [Development](#development)
@@ -120,6 +121,19 @@
120121

121122
---
122123

124+
## Model Strategy
125+
126+
Sketchi uses a multi-model approach via OpenRouter to balance reasoning, vision, and latency.
127+
128+
| Role | Model | Primary Use Case |
129+
|------|-------|------------------|
130+
| **Brain** | `google/gemini-3-flash-preview` | Diagram generation, structural analysis, and visual grading. |
131+
| **Driver** | `google/gemini-2.5-flash-lite` | E2E test execution (Stagehand) and UI interaction. |
132+
| **Experimental** | `google/gemini-3.1-flash-lite-preview` | Low-latency validation, JSON repair, and classification. |
133+
| **Fallback** | `z-ai/glm-4.7` | High-reliability secondary model for diagram retries. |
134+
135+
---
136+
123137
## Features
124138

125139
### Icon Library Generator

apps/web/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,7 @@ SENTRY_LOG_SAMPLE_RATE=0.1 # fraction of logs sampled
2222
SENTRY_CONVEX_ENABLED=0 # enable Convex telemetry (1=on)
2323
SENTRY_CONVEX_MODE=direct # direct|proxy
2424
SKETCHI_TELEMETRY_URL=http://localhost:3001/api/telemetry # telemetry proxy endpoint
25+
SKETCHI_ADMIN_EMAILS=admin@example.com
26+
SKETCHI_ADMIN_SUBJECTS=
27+
SKETCHI_ICON_LIBRARY_EDITOR_EMAILS=anand@shpit.dev
28+
SKETCHI_ICON_LIBRARY_EDITOR_SUBJECTS=

apps/web/src/app/library-generator/[id]/page.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,8 +295,14 @@ export default function LibraryEditorPage({ params }: PageProps) {
295295
<div className="flex flex-col gap-3">
296296
<h2 className="font-semibold text-sm">Upload</h2>
297297
<SvgUploader
298-
isUploading={isUploading || !canEdit}
298+
disabled={!canEdit}
299+
isUploading={isUploading}
299300
onUpload={handleUpload}
301+
statusText={
302+
canEdit
303+
? "SVG only, max 256KB each"
304+
: "Read-only library. Uploads require edit access."
305+
}
300306
/>
301307
</div>
302308
</aside>
@@ -317,8 +323,9 @@ export default function LibraryEditorPage({ params }: PageProps) {
317323
</div>
318324
<div className="flex-1 overflow-y-auto p-4">
319325
<IconGrid
326+
canEdit={canEdit}
320327
icons={icons}
321-
isBusy={isUploading || isSaving || !canEdit}
328+
isBusy={isUploading || isSaving}
322329
onDeleteSelected={handleDeleteSelected}
323330
onMove={handleMove}
324331
styleSettings={styleSettings}

apps/web/src/app/library-generator/page.tsx

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,24 @@ import { toast } from "sonner";
99

1010
import LibraryCard from "@/components/icon-library/library-card";
1111
import { Button } from "@/components/ui/button";
12+
import { Checkbox } from "@/components/ui/checkbox";
1213
import { Input } from "@/components/ui/input";
14+
import { Label } from "@/components/ui/label";
1315

1416
export default function LibraryGeneratorPage() {
1517
const router = useRouter();
1618
const { user } = useAuth();
19+
const viewer = useQuery(api.users.me, user ? {} : "skip");
1720
const libraries = useQuery(api.iconLibraries.list);
1821
const createLibrary = useMutation(api.iconLibraries.create);
1922
const [libraryName, setLibraryName] = useState("");
23+
const [libraryVisibility, setLibraryVisibility] = useState<
24+
"private" | "public"
25+
>("private");
2026
const [isCreating, setIsCreating] = useState(false);
27+
const canManagePublicIconLibraries = Boolean(
28+
viewer?.identity.canManagePublicIconLibraries
29+
);
2130

2231
let createButtonLabel = "Sign in";
2332
if (user) {
@@ -71,7 +80,13 @@ export default function LibraryGeneratorPage() {
7180

7281
try {
7382
const name = libraryName.trim() || "Untitled Library";
74-
const id = await createLibrary({ name, visibility: "private" });
83+
const id = await createLibrary({
84+
name,
85+
visibility:
86+
canManagePublicIconLibraries && libraryVisibility === "public"
87+
? "public"
88+
: "private",
89+
});
7590
router.push(`/library-generator/${id}`);
7691
} catch (error) {
7792
const message =
@@ -100,6 +115,23 @@ export default function LibraryGeneratorPage() {
100115
Sign in to create private libraries and upload icons.
101116
</p>
102117
)}
118+
{user && canManagePublicIconLibraries ? (
119+
<div className="flex items-center gap-2">
120+
<Checkbox
121+
checked={libraryVisibility === "public"}
122+
id="library-visibility-public"
123+
onCheckedChange={(checked) =>
124+
setLibraryVisibility(checked === true ? "public" : "private")
125+
}
126+
/>
127+
<Label
128+
className="text-muted-foreground text-sm"
129+
htmlFor="library-visibility-public"
130+
>
131+
Create as public library
132+
</Label>
133+
</div>
134+
) : null}
103135
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
104136
<Input
105137
className="border-2 shadow-sm"

apps/web/src/app/opencode/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ export default function OpenCodeDocsPage() {
354354
<div className="size-3 rounded-full bg-[#ffbd2e]" />
355355
<div className="size-3 rounded-full bg-[#27c93f]" />
356356
<span className="ml-2 font-medium text-white/50 text-xs">
357-
run command
357+
run
358358
</span>
359359
</div>
360360
<div className="flex items-center gap-2">

apps/web/src/components/icon-library/icon-grid.tsx

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface IconGridItem {
1414
}
1515

1616
interface IconGridProps {
17+
canEdit?: boolean;
1718
icons: IconGridItem[];
1819
isBusy?: boolean;
1920
onDeleteSelected: (ids: string[]) => void;
@@ -22,6 +23,7 @@ interface IconGridProps {
2223
}
2324

2425
export default function IconGrid({
26+
canEdit = true,
2527
icons,
2628
onDeleteSelected,
2729
onMove,
@@ -66,6 +68,41 @@ export default function IconGrid({
6668
);
6769
}
6870

71+
let actionControls = (
72+
<span className="text-muted-foreground text-xs">Read-only</span>
73+
);
74+
75+
if (isEditMode) {
76+
actionControls = (
77+
<>
78+
<Button onClick={exitEditMode} size="sm" type="button" variant="ghost">
79+
Cancel
80+
</Button>
81+
<Button
82+
disabled={selectedIds.size === 0 || isBusy}
83+
onClick={handleDeleteSelected}
84+
size="sm"
85+
type="button"
86+
variant="destructive"
87+
>
88+
Delete Selected ({selectedIds.size})
89+
</Button>
90+
</>
91+
);
92+
} else if (canEdit) {
93+
actionControls = (
94+
<Button
95+
disabled={isBusy}
96+
onClick={() => setIsEditMode(true)}
97+
size="sm"
98+
type="button"
99+
variant="outline"
100+
>
101+
Edit
102+
</Button>
103+
);
104+
}
105+
69106
return (
70107
<div className="flex flex-col gap-3">
71108
<div className="flex items-center justify-end gap-2">
@@ -91,37 +128,7 @@ export default function IconGrid({
91128
<Plus />
92129
</Button>
93130
</div>
94-
{isEditMode ? (
95-
<>
96-
<Button
97-
onClick={exitEditMode}
98-
size="sm"
99-
type="button"
100-
variant="ghost"
101-
>
102-
Cancel
103-
</Button>
104-
<Button
105-
disabled={selectedIds.size === 0 || isBusy}
106-
onClick={handleDeleteSelected}
107-
size="sm"
108-
type="button"
109-
variant="destructive"
110-
>
111-
Delete Selected ({selectedIds.size})
112-
</Button>
113-
</>
114-
) : (
115-
<Button
116-
disabled={isBusy}
117-
onClick={() => setIsEditMode(true)}
118-
size="sm"
119-
type="button"
120-
variant="outline"
121-
>
122-
Edit
123-
</Button>
124-
)}
131+
{actionControls}
125132
</div>
126133

127134
<div
@@ -178,7 +185,7 @@ export default function IconGrid({
178185
{!isEditMode && (
179186
<div className="flex items-center gap-1">
180187
<Button
181-
disabled={isBusy || !canMoveLeft}
188+
disabled={isBusy || !canEdit || !canMoveLeft}
182189
onClick={() => onMove(icon.id, "left")}
183190
size="xs"
184191
type="button"
@@ -187,7 +194,7 @@ export default function IconGrid({
187194
188195
</Button>
189196
<Button
190-
disabled={isBusy || !canMoveRight}
197+
disabled={isBusy || !canEdit || !canMoveRight}
191198
onClick={() => onMove(icon.id, "right")}
192199
size="xs"
193200
type="button"

0 commit comments

Comments
 (0)