Skip to content

feat: share themes by URL — CLI theme add, gallery import + delete - #286

Open
ridemountainpig wants to merge 6 commits into
1weiho:mainfrom
ridemountainpig:feat/theme-import-from-url
Open

feat: share themes by URL — CLI theme add, gallery import + delete#286
ridemountainpig wants to merge 6 commits into
1weiho:mainfrom
ridemountainpig:feat/theme-import-from-url

Conversation

@ridemountainpig

@ridemountainpig ridemountainpig commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds theme sharing between open-slide sites via URL:

  • open-slide theme add <url> — new CLI command that imports a theme from another open-slide site into the local project.
  • Themes gallery import dialog — import a theme by URL directly from the Themes panel in the app.
  • Copy theme URL — copy a shareable URL for any theme from the theme detail view.
  • Delete theme — remove a theme from the gallery.

Includes new theme scan/import modules in core, a /api/themes Vite route, locale strings (en/ja/zh-cn/zh-tw), docs for the new CLI command and theme sharing, and a minor changeset for @open-slide/core.

Test plan

  • pnpm check — clean (one pre-existing warning on main, untouched)
  • pnpm typecheck — passes
  • pnpm test — 284 tests passing

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added theme sharing and importing via URL in the web UI and CLI, including “copy theme URL”, an import dialog with discovered-theme selection, and theme deletion from the Themes gallery with confirmation.
    • Added open-slide theme add <url> with flags for --id, --all, --force, and -y/--yes.
  • Documentation
    • Added CLI docs and updated theme-sharing guidance, including how to restrict import sources.
  • Bug Fixes
    • Improved safety for importing, handling name/id collisions, and removing theme references from slides when deleting a theme.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 5, 2026

Copy link
Copy Markdown

@ridemountainpig is attempting to deploy a commit to the open-slide Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR adds URL-based theme sharing across the CLI, dev server, and themes UI, including remote discovery/import, copying and deleting themes, slide reference cleanup, host restrictions, build manifests, localization, and documentation.

Changes

Theme import/sharing feature

Layer / File(s) Summary
Theme scanning and build assets
packages/core/src/themes/scan.ts, packages/core/src/vite/themes-plugin.ts
Adds shared theme scanning and manifest generation, then emits theme Markdown, demo, and manifest assets during builds.
Remote theme import core
packages/core/src/themes/import.ts
Validates remote sources, discovers and fetches themes, probes demos, enforces limits, handles collisions, and writes local theme files.
Configuration and CLI command
packages/core/src/config.ts, packages/core/src/app/virtual.d.ts, packages/core/src/cli/*
Adds host restrictions and wires open-slide theme add with selection, confirmation, and overwrite flags.
Dev-server theme routes
packages/core/src/vite/config.ts, packages/core/src/vite/routes/*, packages/core/src/vite/api-plugin.ts
Serves theme assets and provides import/delete endpoints backed by the configured themes directory.
Slide reference cleanup
packages/core/src/editing/slide-ops.ts, packages/core/src/editing/slide-ops.test.ts
Removes matching top-level meta.theme properties when themes are deleted and tests formatting and edge cases.
Theme gallery actions
packages/core/src/app/components/themes/*, packages/core/src/app/routes/themes.tsx
Adds copy-URL, import, and development-only delete actions with dialogs and toast feedback.
Localized theme UI
packages/core/src/locale/types.ts, packages/core/src/locale/{en,ja,zh-cn,zh-tw}.ts
Adds localized strings for theme import, URL copying, and deletion.
Documentation and release metadata
apps/web/content/docs/**, packages/core/README.md, .changeset/*
Documents the new CLI and sharing workflows, configuration, and release information.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ThemeImportDialog
  participant registerThemeRoutes
  participant discoverThemes
  participant fetchTheme
  participant writeTheme
  ThemeImportDialog->>registerThemeRoutes: POST /__themes/import
  registerThemeRoutes->>discoverThemes: discover source URL
  registerThemeRoutes->>fetchTheme: fetch selected themes
  registerThemeRoutes->>writeTheme: write themes locally
  writeTheme-->>registerThemeRoutes: written results
  registerThemeRoutes-->>ThemeImportDialog: import response
Loading
sequenceDiagram
  participant ThemesGallery
  participant registerThemeRoutes
  participant clearThemeFromSlides
  ThemesGallery->>registerThemeRoutes: DELETE /__themes/:id
  registerThemeRoutes->>clearThemeFromSlides: clear slide references
  clearThemeFromSlides-->>registerThemeRoutes: cleared slide IDs
  registerThemeRoutes-->>ThemesGallery: deletion response
Loading

Possibly related PRs

Suggested reviewers: 1weiho

Poem

A theme hops in from far away,
Then finds a home without delay.
Links are copied, old ties undone,
Safe little imports for everyone.
🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: URL-based theme sharing with CLI import, gallery import, and delete support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (3)
apps/web/content/docs/reference/config.mdx (1)

44-55: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: type definition order is inconsistent with existing pattern.

OpenSlideThemeImportConfig is used before its definition, while OpenSlideBuildConfig above follows define-then-use ordering. Consider moving the new type block before OpenSlideConfig for consistency.

🤖 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 `@apps/web/content/docs/reference/config.mdx` around lines 44 - 55, The type
declaration order is inconsistent because OpenSlideThemeImportConfig is
referenced before it is defined. Move the OpenSlideThemeImportConfig block so it
appears before OpenSlideConfig, keeping the define-then-use pattern used by
OpenSlideBuildConfig and preserving the existing type names and references.
packages/core/src/cli/run.ts (1)

143-156: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Consider test coverage for the new theme add command.

themeAdd covers host-trust confirmation, interactive selection, and fetch/write flows — all security/UX-sensitive paths — but no corresponding test file was found alongside theme.ts (only an unrelated import shown in run.test.ts).

🤖 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 `@packages/core/src/cli/run.ts` around lines 143 - 156, Add test coverage for
the new theme add command and its `themeAdd` flow, since it exercises security-
and UX-sensitive behavior. Create or extend tests around `themeAdd` in
`theme.ts` to cover host-trust confirmation, interactive selection, and
fetch/write success and failure paths. Also verify the `theme` command wiring in
`run.ts` invokes `themeAdd` correctly from the `add` subcommand. Reuse the
existing `themeAdd` symbol and the `theme` command setup to place the tests near
the changed behavior.
packages/core/src/vite/routes/themes.ts (1)

10-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the endpoint-list comment.

This block restates the routes; the later comment on Lines 17-19 already captures the non-obvious reason for the GET routes.

Proposed cleanup
-// POST   /__themes/import     { url, id?, ids?, force? }   fetch themes from a URL into themes/
-//                             — multi-theme sources answer { discovered } and import
-//                               nothing until the client re-posts with ids
-// DELETE /__themes/:id                               remove a theme's files + clear meta.theme from slides using it
-// GET    /themes/index.json                          theme manifest (mirrors the build output)
-// GET    /themes/:file                               raw theme .md / .demo.* source
-//

As per coding guidelines, **/*.{ts,tsx,js,jsx} comments should not explain WHAT the code does or add module-header descriptions.

🤖 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 `@packages/core/src/vite/routes/themes.ts` around lines 10 - 16, The
module-header endpoint list comment in themes.ts should be removed because it
restates the routes and violates the comment guidelines. Delete the block near
the top of the file and keep the later inline comment that explains the
non-obvious GET route behavior; use the surrounding route definitions in the
routes module to locate it.

Source: Coding guidelines

🤖 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 `@packages/core/src/app/components/themes/theme-detail.tsx`:
- Around line 79-100: The copied share link in handleCopyUrl currently
interpolates theme.id directly, which can break for spaces or other unsafe
characters. Update the URL construction in theme-detail.tsx so the /themes/<id>
segment uses an encoded theme.id while keeping the same route shape, and leave
the rest of the clipboard/toast flow unchanged.

In `@packages/core/src/app/components/themes/themes-gallery.tsx`:
- Around line 62-70: The theme gallery menu trigger stays hidden for keyboard
users because it only reveals on hover or when expanded; update the button in
ThemesGallery to also become visible on keyboard focus. Add a focus-based reveal
class such as focus-visible or group-focus-within to the same trigger styles so
tabbing to the DropdownMenuTrigger button shows it before activation, while
keeping the existing hover and aria-expanded behavior intact.

In `@packages/core/src/cli/theme.ts`:
- Around line 34-80: The cancel path in selectEntries is inconsistent with the
trust-confirmation flow in themeAdd: blank input currently throws
ThemeImportError('invalid', 'Cancelled.') instead of aborting cleanly. Update
selectEntries to return a sentinel value such as null or an empty result when
the user cancels, then handle that case in themeAdd the same way the
confirmation decline is handled so it prints the dim “Cancelled.” message and
returns normally. Keep the change localized around selectEntries and the
themeAdd caller, preserving the existing selection behavior for valid inputs.

In `@packages/core/src/editing/slide-ops.ts`:
- Around line 226-232: The theme-removal logic in slide-ops.ts is matching any
theme property inside the meta object, so update the edit path in the relevant
helper to only target top-level meta.theme and ignore nested properties like
nested.theme. Replace the regex-based search in the code around the propRe/match
check with a top-level-aware scan or AST-based edit so the deletion only occurs
when the actual top-level theme field matches the deleted theme ID.

In `@packages/core/src/themes/import.ts`:
- Around line 321-372: `writeTheme` has a TOCTOU race because `availableThemeId`
probes with `existsSync` before `fs.writeFile` writes the theme file, so
concurrent imports can clobber each other. Update the `writeTheme` flow to avoid
the separate existence check when not forcing: attempt the write with an
exclusive create mode and, on `EEXIST`, retry with the next numbered suffix
until a free id is successfully written. Keep `availableThemeId` and the
`writeTheme` rename logic aligned with this retry-based approach.
- Around line 297-299: The formatFrontmatterValue helper only quotes values in a
couple of cases, so YAML-significant characters like colons or # can still be
emitted unescaped into frontmatter. Update formatFrontmatterValue in import.ts
to always serialize the value safely for YAML, and make sure the renaming path
that writes name: <value> uses this helper so theme names with special
characters stay valid.

In `@packages/core/src/themes/scan.ts`:
- Around line 71-76: The demo candidate lookup in scanTheme should not rely on
existsSync alone, since it can match directories and incorrectly set demoAbs.
Update the DEMO_EXTS loop in scan.ts to verify each candidate is a file before
assigning demoAbs, using the same path-building logic and filtering out only
non-file or missing paths so the build plugin only receives valid demo files.

In `@packages/core/src/vite/routes/themes.ts`:
- Around line 126-171: The catch path in the themes import handler is treating
malformed JSON from readBody as a server error, so update the request handling
in the themes route to detect body-parse failures and return a 400 instead of
falling through to the generic 500 response. Use the existing ThemeImportError
branch and the surrounding try/catch in the route that calls readBody,
discoverThemes, and fetchTheme to add a specific malformed-body case before the
final fallback.
- Around line 46-49: The theme route currently calls decodeURIComponent before
the try block, so malformed percent-encoded paths can escape the handler’s error
handling. Move the decode logic into the try in the themes route handler (the
code that builds url and rel) and catch URIError explicitly so invalid theme
paths return a 400 response instead of failing outside the route.

---

Nitpick comments:
In `@apps/web/content/docs/reference/config.mdx`:
- Around line 44-55: The type declaration order is inconsistent because
OpenSlideThemeImportConfig is referenced before it is defined. Move the
OpenSlideThemeImportConfig block so it appears before OpenSlideConfig, keeping
the define-then-use pattern used by OpenSlideBuildConfig and preserving the
existing type names and references.

In `@packages/core/src/cli/run.ts`:
- Around line 143-156: Add test coverage for the new theme add command and its
`themeAdd` flow, since it exercises security- and UX-sensitive behavior. Create
or extend tests around `themeAdd` in `theme.ts` to cover host-trust
confirmation, interactive selection, and fetch/write success and failure paths.
Also verify the `theme` command wiring in `run.ts` invokes `themeAdd` correctly
from the `add` subcommand. Reuse the existing `themeAdd` symbol and the `theme`
command setup to place the tests near the changed behavior.

In `@packages/core/src/vite/routes/themes.ts`:
- Around line 10-16: The module-header endpoint list comment in themes.ts should
be removed because it restates the routes and violates the comment guidelines.
Delete the block near the top of the file and keep the later inline comment that
explains the non-obvious GET route behavior; use the surrounding route
definitions in the routes module to locate it.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 32668155-c0ea-4ae6-b850-45a169ee190d

📥 Commits

Reviewing files that changed from the base of the PR and between e09d8ba and 73567a4.

📒 Files selected for processing (29)
  • .changeset/theme-import-from-url.md
  • apps/web/content/docs/cli/meta.json
  • apps/web/content/docs/cli/overview.mdx
  • apps/web/content/docs/cli/theme-add.mdx
  • apps/web/content/docs/core-feature/themes.mdx
  • apps/web/content/docs/reference/config.mdx
  • packages/core/README.md
  • packages/core/src/app/components/themes/theme-detail.tsx
  • packages/core/src/app/components/themes/theme-import-dialog.tsx
  • packages/core/src/app/components/themes/themes-gallery.tsx
  • packages/core/src/app/routes/themes.tsx
  • packages/core/src/app/virtual.d.ts
  • packages/core/src/cli/run.ts
  • packages/core/src/cli/theme.ts
  • packages/core/src/config.ts
  • packages/core/src/editing/slide-ops.test.ts
  • packages/core/src/editing/slide-ops.ts
  • packages/core/src/locale/en.ts
  • packages/core/src/locale/ja.ts
  • packages/core/src/locale/types.ts
  • packages/core/src/locale/zh-cn.ts
  • packages/core/src/locale/zh-tw.ts
  • packages/core/src/themes/import.ts
  • packages/core/src/themes/scan.ts
  • packages/core/src/vite/api-plugin.ts
  • packages/core/src/vite/config.ts
  • packages/core/src/vite/routes/context.ts
  • packages/core/src/vite/routes/themes.ts
  • packages/core/src/vite/themes-plugin.ts

Comment thread packages/core/src/app/components/themes/theme-detail.tsx
Comment thread packages/core/src/app/components/themes/themes-gallery.tsx Outdated
Comment thread packages/core/src/cli/theme.ts
Comment thread packages/core/src/editing/slide-ops.ts
Comment thread packages/core/src/themes/import.ts
Comment thread packages/core/src/themes/import.ts Outdated
Comment thread packages/core/src/themes/scan.ts
Comment thread packages/core/src/vite/routes/themes.ts
Comment thread packages/core/src/vite/routes/themes.ts
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 `@packages/core/src/editing/slide-ops.ts`:
- Around line 196-213: The scanner in nestingDepthAt currently ignores comments,
so matches inside // or /* */ can be treated as top-level and rewrite comment
text instead of real metadata. Update nestingDepthAt to track line and block
comment state before counting a match as depth 0, and apply the same fix
anywhere else in slide-ops.ts that reuses this scanner logic so only actual code
tokens are considered. Reference the nestingDepthAt helper and the related
match-handling logic around the affected slide operations when making the
change.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c139629b-529c-4498-afbb-9a35351e5684

📥 Commits

Reviewing files that changed from the base of the PR and between 73567a4 and 148b597.

📒 Files selected for processing (9)
  • apps/web/content/docs/reference/config.mdx
  • packages/core/src/app/components/themes/theme-detail.tsx
  • packages/core/src/app/components/themes/themes-gallery.tsx
  • packages/core/src/cli/theme.ts
  • packages/core/src/editing/slide-ops.test.ts
  • packages/core/src/editing/slide-ops.ts
  • packages/core/src/themes/import.ts
  • packages/core/src/themes/scan.ts
  • packages/core/src/vite/routes/themes.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/core/src/app/components/themes/theme-detail.tsx
  • apps/web/content/docs/reference/config.mdx
  • packages/core/src/editing/slide-ops.test.ts
  • packages/core/src/cli/theme.ts
  • packages/core/src/app/components/themes/themes-gallery.tsx
  • packages/core/src/vite/routes/themes.ts
  • packages/core/src/themes/scan.ts

Comment thread packages/core/src/editing/slide-ops.ts Outdated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/editing/slide-ops.ts (1)

246-259: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Brace matching needs to ignore strings and comments.
A } inside a valid string or comment truncates body early, so removeMetaThemeFromSource returns null and clearThemeFromSlides skips removing meta.theme for that slide. Reuse the quote/comment-aware scan from syntaxStateAt here.

🤖 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 `@packages/core/src/editing/slide-ops.ts` around lines 246 - 259, The brace
scan in removeMetaThemeFromSource currently counts every “{” and “}”, so a brace
inside a string or comment can end the body too early. Update this scan to reuse
the quote/comment-aware logic from syntaxStateAt, so only real code braces
affect depth and closeBrace detection. Keep the behavior in clearThemeFromSlides
intact by ensuring removeMetaThemeFromSource returns the correct body instead of
null when braces appear inside strings or comments.
🤖 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.

Outside diff comments:
In `@packages/core/src/editing/slide-ops.ts`:
- Around line 246-259: The brace scan in removeMetaThemeFromSource currently
counts every “{” and “}”, so a brace inside a string or comment can end the body
too early. Update this scan to reuse the quote/comment-aware logic from
syntaxStateAt, so only real code braces affect depth and closeBrace detection.
Keep the behavior in clearThemeFromSlides intact by ensuring
removeMetaThemeFromSource returns the correct body instead of null when braces
appear inside strings or comments.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9f0cbac0-d5a4-4d36-be6e-d937474dade3

📥 Commits

Reviewing files that changed from the base of the PR and between 148b597 and 834eca0.

📒 Files selected for processing (2)
  • packages/core/src/editing/slide-ops.test.ts
  • packages/core/src/editing/slide-ops.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/editing/slide-ops.test.ts

ridemountainpig and others added 2 commits July 5, 2026 15:43
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolves the config.mdx conflict by expressing themeImport in the new
TypeTable docs format, and migrates the theme import dialog + gallery
menu triggers to the Base UI render prop API.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keeps both host options in OpenSlideConfig: upstream's top-level
allowedHosts (Vite dev-server host check) and this PR's
themeImport.allowedHosts (theme import restriction).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant