Context
The new tweakcn import flow (app/src/themes/tweakcn_import.rs::parse_blocks) parses the CSS-block format users get from tweakcn's "Copy CSS" button:
```css
:root { --background: oklch(...); ... }
.dark { --background: oklch(...); ... }
```
But tweakcn also publishes themes as a shadcn registry JSON at stable URLs like https://tweakcn.com/r/themes/<name>.json (e.g. /r/themes/vercel.json):
```json
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "vercel",
"type": "registry:style",
"cssVars": {
"theme": { "font-sans": "Geist, sans-serif", ... },
"light": { "background": "oklch(0.99 0 0)", "foreground": "oklch(0 0 0)", ... },
"dark": { "background": "oklch(0 0 0)", "foreground": "oklch(1 0 0)", ... }
}
}
```
The editor-URL pattern users see in their browser (https://tweakcn.com/editor/theme?theme=vercel) maps deterministically to https://tweakcn.com/r/themes/vercel.json.
Why this matters
Users who hand a Cast-Codes contributor a tweakcn share URL today have to:
- Open tweakcn.com
- Click "Code" / "Copy CSS"
- Paste into the import modal
Each manual step is a place to drop content. Worse, the modal currently chokes on full pastes (issue #91). If we supported the JSON path:
- Drag a
.json file onto the modal directly, OR
- Paste a tweakcn URL into the modal and we fetch + extract (still local-only —
https://tweakcn.com is the user's choice of source, not a Cast-Codes hosted dependency).
Suggested implementation
Add a sibling parser at parse_registry_json(input: &str) -> Result<ParsedBlocks, ImportError> in app/src/themes/tweakcn_import.rs:
```rust
pub fn parse_blocks_or_json(input: &str) -> Result<ParsedBlocks, ImportError> {
let trimmed = input.trim_start();
if trimmed.starts_with('{') {
parse_registry_json(input)
} else {
parse_blocks(input)
}
}
fn parse_registry_json(input: &str) -> Result<ParsedBlocks, ImportError> {
let v: serde_json::Value = serde_json::from_str(input)
.map_err(|e| ImportError::Io(format!("invalid JSON: {e}")))?;
let css_vars = v.get("cssVars").ok_or(ImportError::NoColorBlocksFound)?;
let name = v.get("name").and_then(|n| n.as_str()).map(String::from);
let mut blocks = ParsedBlocks { name_comment: name, ..Default::default() };
for (mode_key, target) in [("light", &mut blocks.light), ("dark", &mut blocks.dark)] {
if let Some(map) = css_vars.get(mode_key).and_then(|m| m.as_object()) {
for (k, v) in map {
let s = match v.as_str() { Some(s) => s, None => continue };
// Same oklch(L C H) parsing as parse_blocks's parse_decls closure.
let Some(args) = s.strip_prefix("oklch(").and_then(|t| t.strip_suffix(')')) else { continue };
// … reuse the triple-parsing logic …
}
}
}
if blocks.light.is_empty() && blocks.dark.is_empty() {
return Err(ImportError::NoColorBlocksFound);
}
Ok(blocks)
}
```
Then wire the modal's on_css_changed to call parse_blocks_or_json. Drag-drop already handles .css; extend the extension check to allow .json too.
URL-fetch is more involved (network call from the OSS build — needs to clear the [castcodes-fork-local-boundary](skill: castcodes-fork-local-boundary) check). A safer middle step: if the user pastes https://tweakcn.com/..., surface a hint "Paste the JSON from the registry URL instead of the share URL" with a click-to-copy curl command.
Discovery context
Validated during smoke test of branch castcodes/theme-tweakcn-impl. The user pasted a Vercel-theme tweakcn URL into the modal expecting it to work; the modal showed CSS but with so much overhead the user gave up and asked us to extract via URL externally. I curl'd /r/themes/vercel.json and converted to CSS via a one-off Python script, then ran through parse_blocks + to_warp_theme — output landed cleanly in ~/.cast-codes/themes/vercel.yaml. Adding native JSON support would have eliminated the conversion step.
Context
The new tweakcn import flow (
app/src/themes/tweakcn_import.rs::parse_blocks) parses the CSS-block format users get from tweakcn's "Copy CSS" button:```css
:root { --background: oklch(...); ... }
.dark { --background: oklch(...); ... }
```
But tweakcn also publishes themes as a shadcn registry JSON at stable URLs like
https://tweakcn.com/r/themes/<name>.json(e.g./r/themes/vercel.json):```json
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "vercel",
"type": "registry:style",
"cssVars": {
"theme": { "font-sans": "Geist, sans-serif", ... },
"light": { "background": "oklch(0.99 0 0)", "foreground": "oklch(0 0 0)", ... },
"dark": { "background": "oklch(0 0 0)", "foreground": "oklch(1 0 0)", ... }
}
}
```
The editor-URL pattern users see in their browser (
https://tweakcn.com/editor/theme?theme=vercel) maps deterministically tohttps://tweakcn.com/r/themes/vercel.json.Why this matters
Users who hand a Cast-Codes contributor a tweakcn share URL today have to:
Each manual step is a place to drop content. Worse, the modal currently chokes on full pastes (issue #91). If we supported the JSON path:
.jsonfile onto the modal directly, ORhttps://tweakcn.comis the user's choice of source, not a Cast-Codes hosted dependency).Suggested implementation
Add a sibling parser at
parse_registry_json(input: &str) -> Result<ParsedBlocks, ImportError>inapp/src/themes/tweakcn_import.rs:```rust
pub fn parse_blocks_or_json(input: &str) -> Result<ParsedBlocks, ImportError> {
let trimmed = input.trim_start();
if trimmed.starts_with('{') {
parse_registry_json(input)
} else {
parse_blocks(input)
}
}
fn parse_registry_json(input: &str) -> Result<ParsedBlocks, ImportError> {
let v: serde_json::Value = serde_json::from_str(input)
.map_err(|e| ImportError::Io(format!("invalid JSON: {e}")))?;
let css_vars = v.get("cssVars").ok_or(ImportError::NoColorBlocksFound)?;
let name = v.get("name").and_then(|n| n.as_str()).map(String::from);
}
```
Then wire the modal's
on_css_changedto callparse_blocks_or_json. Drag-drop already handles.css; extend the extension check to allow.jsontoo.URL-fetch is more involved (network call from the OSS build — needs to clear the [castcodes-fork-local-boundary](skill: castcodes-fork-local-boundary) check). A safer middle step: if the user pastes
https://tweakcn.com/..., surface a hint "Paste the JSON from the registry URL instead of the share URL" with a click-to-copy curl command.Discovery context
Validated during smoke test of branch
castcodes/theme-tweakcn-impl. The user pasted a Vercel-theme tweakcn URL into the modal expecting it to work; the modal showed CSS but with so much overhead the user gave up and asked us to extract via URL externally. I curl'd/r/themes/vercel.jsonand converted to CSS via a one-off Python script, then ran throughparse_blocks+to_warp_theme— output landed cleanly in~/.cast-codes/themes/vercel.yaml. Adding native JSON support would have eliminated the conversion step.