SuperAPI AI Directory

open-slide-best-practices

General & other

View repository

Updated 10-16 08:42

About this skill

open-slide Best Practices for Israeli Developers

Adapted from 1weiho/open-slide (MIT). Hebrew and RTL adaptations by skills-il.

Problem

open-slide ships an opinionated authoring system: a fixed 1920×1080 React canvas, a typed DesignSystem const, a themes folder, an inspector that drops @slide-comment markers in source, and five built-in skills (slide-authoring, create-slide, create-theme, apply-comments, current-slide). None of those touch Hebrew or RTL. Out of the box, a deck written by a coding agent comes out left-aligned, system-font only, with paddingLeft that fights the Hebrew reading order, and no guidance for mixed Hebrew+English text. Israeli developers waste hours retrofitting RTL, loading Hebrew Google Fonts, and discovering that the type scale (140-200px hero) which works in Inter looks cramped in Heebo at the same weight.

Instructions

When to use

Use this skill whenever you are writing or editing code under slides/<id>/ in an open-slide project, creating a theme under themes/, or processing inspector comments, especially when any of that content is in Hebrew or bilingual.

New project setup

When in an empty folder or workspace with no existing open-slide project, scaffold one using:

npx @open-slide/cli init my-slide
cd my-slide
npm run dev

Replace my-slide with a suitable project name. The scaffolder installs dependencies with npm and prints npm run dev as the next step; pnpm dev works too if you re-install with pnpm.

Node version. @open-slide/cli depends on commander@15, which declares "node": ">=22.12.0", and on chalk@6 (">=22"). On Node 20 the install still completes but npm prints EBADENGINE Unsupported engine for both. Use Node 22.12 or newer.

What the scaffold gives you:

Path What it is
slides/<id>/index.tsx One folder per slide. See the id rule below.
themes/ Theme markdown files
open-slide.config.ts Typed OpenSlideConfig export, empty by default. Framework-level options live here: base, slidesDir, themesDir, assetsDir, port, allowedHosts, build, and a deprecated locale.
.agents/skills/ and .claude/skills/ The five upstream skills, copied in at init time
npm run sync:skills Re-copies the bundled skills out of @open-slide/core after a core upgrade

Run npm run sync:skills after upgrading @open-slide/core, otherwise the agent keeps reading the skill copies from the version you scaffolded with.

Which version you are on

Target @open-slide/core 1.19.x. That is what npx @open-slide/cli init installs today, and everything in this skill is verified against 1.19.1.

A 2.0.0 beta is published (React 19, Vite 8, TypeScript 7). It is a pre-release: npm's latest tag still resolves to 1.19.x and the beta sits behind its own beta dist-tag, so you only reach v2 by asking for it (@open-slide/cli@2.0.0-beta.1 or @beta). Do not scaffold production work onto it, and do not assume any v2 API detail is stable.

Your slide source is not the problem. The upstream migration guide is explicit that v1 slides need no changes and that the migration is confined to package.json, your Node version, and your deploy config. Everything this skill teaches about the file contract, the canvas, type scale, themes and Hebrew/RTL carries over unchanged.

The one thing that breaks on upgrade. A v1 scaffold puts vite in your devDependencies; core 2.x ships its own Vite 8 internally. Leave the old entry in place and the dev server and build both refuse to start:

✖ @tailwindcss/vite resolves vite@5.4.21 (node_modules/vite), but @open-slide/core ships
vite@8.2.2 (node_modules/@open-slide/core/node_modules/vite).
A `vite` entry in your package.json (v1 workspaces had one) shadows the copy core depends on.
Remove it and reinstall.

Delete the vite devDependency and reinstall. Fresh v2 scaffolds omit it and are unaffected. Migration guide: https://open-slide.dev/docs/migrate-to-v2.

Slide folder ids must be ASCII (Hebrew folder names are silently dropped)

Do not name a slide folder in Hebrew. Discovery filters every folder under slides/ through SLIDE_ID_RE = /^[a-z0-9_-]+$/i and skips the rest, so a folder like slides/פתיחה/ never reaches the sidebar, the browser, or the build. Re-verified on @open-slide/core@1.19.1 (the current stable release) by scaffolding a project and running npm run build:

[plugin:open-slide] [plugin open-slide] Ignoring slide folder "פתיחה": slide ids must match
/^[a-z0-9_-]+$/i (lowercase/uppercase letters, digits, "-", "_"). Rename the folder under
"slides/" to a kebab-case id so it appears in the browser and can be moved into folders.

It is only a warning, not an error, so npm run build exits 0 and the missing slide is easy to miss. Use a Latin kebab-case id (peticha, cover, q2-roadmap) and put the Hebrew in meta.title and in the page content, where it belongs.

Do not over-apply this rule. It is specific to slide folder ids. Verified as NOT affected:

Surface Hebrew allowed? Why
slides/<id>/ folder name No Filtered by SLIDE_ID_RE at discovery
meta.title Yes Free text, never used as an id
themes/<id>.md file name Yes The theme id is path.basename(file, '.md') with no ASCII filter, and theme routes are built with encodeURIComponent(id)
Theme frontmatter values (name:, description:) Yes Only the frontmatter KEY is matched against /^([A-Za-z0-9_-]+)\s*:/; the value is free text
Slide body copy Yes It is just JSX text

Hebrew and RTL

For any Hebrew or bilingual deck, load ./rules/hebrew-rtl.md first. It covers:

  • Hebrew Google Fonts (Heebo, Rubik, Assistant, Noto Sans Hebrew) loaded by head injection
  • The <html dir="rtl"> set-up vs per-page dir attribute (the canvas root is the page component, not the document)
  • CSS logical properties (paddingInlineStart, marginInlineStart, insetInlineStart) instead of paddingLeft/marginLeft/left
  • flexDirection: 'row-reverse' vs setting dir on the flex container
  • Bidirectional text with <bdi> for mixed Hebrew + Latin (brand names, code, URLs)
  • Hebrew-aware type scale tuning (Hebrew renders ~10-15% wider per character at the same px size)
  • Hebrew DesignSystem.fonts.display and fonts.body choices that work at hero size
  • RTL-aware theme markdown patterns under themes/
  • Hebrew typography pitfalls: nikkud rendering, sofit letter spacing, hyphen vs maqaf

Slide authoring (file contract, canvas, type scale, themes, design tokens)

For the technical reference on what goes inside slides/<id>/index.tsx, load ./rules/slide-authoring.md. This is the same content the upstream slide-authoring skill exposes, with Hebrew/RTL pointers inline. It covers the file contract, the 1920×1080 canvas math, the type scale, spacing, visual direction, themes, the DesignSystem design-tokens object, the starter template, assets, image placeholders, and the repeated-elements rule.

Upstream has since split its own slide-authoring skill into per-primitive reference files. Three of them cover primitives this skill does not restate, so read them upstream rather than reimplementing by hand:

Upstream reference Primitive Why it matters in a Hebrew deck
references/steps.md <Steps> / <Step> stepped reveals Staged reveals let you keep each Hebrew beat short instead of overflowing one dense page
references/page-numbers.md useSlidePageNumber() Read { current, total } from the hook instead of hardcoding a footer counter; wrap the Latin digits in <bdi> inside an RTL footer
references/morph.md MorphElement shared-element ("magic move") transitions Morph interpolates position and size across the cut, so an element that moves between an LTR and an RTL page needs the same id on both

Drafting a new deck

When the user asks for "make slides about X" / "create a presentation" / "draft a deck", load ./rules/create-slide.md for the workflow: theme picker, the four scoping questions (aesthetic direction, page count, text density, motion), slide id selection, structure planning, visual commitment, write, self-review, hand off.

Creating a theme

When the user asks to "create a theme" / "make a theme called X" / "extract a theme from this slide", load ./rules/create-theme.md. A theme is a two-file bundle: themes/<id>.md (Palette / Typography / Layout / Fixed components / Motion / Aesthetic) plus a paired demo module themes/<id>.demo.tsx (.jsx, .ts and .js are also accepted), a runnable 2-3 page mini-slide that the dev UI's Themes panel renders as the live preview. Writing only the markdown leaves the theme with a dead preview card.

Applying inspector comments

When the user has clicked on a rendered page in the dev server and added comments (via the in-browser inspector tool), and asks to "apply comments" / "process slide comments", load ./rules/apply-comments.md. It covers the {/* @slide-comment */} marker format, the detection regex, the base64url-decoded payload, and the apply-in-reverse-line-order procedure.

Resolving deictic references ("this page", "the slide I'm on")

When the user references the current slide without naming it, load ./rules/current-slide.md first. It explains how to read the live cursor at node_modules/.open-slide/current.json, what fields are in it, and the staleness rules. Re-read it on every deictic turn, the user navigates between turns, so a value you read earlier is almost certainly stale.

Speaker notes go in the notes export, never in a markdown file

open-slide has built-in speaker notes, and coding agents routinely miss them: asked for a talk track, an agent writes script.md or notes.md next to the slide, which the runtime never reads. The presenter view stays empty and the user assumes the feature is missing.

Notes are an optional notes export in slides/<id>/index.tsx, index-aligned with the default page array. SlideModule declares it as notes?: (string | undefined)[]. Use undefined to skip a page rather than shifting the array:

const Cover: Page = () => <div dir="rtl">שלום</div>;
const Agenda: Page = () => <div dir="rtl">סדר היום</div>;

export const notes: (string | undefined)[] = [
  'פתחו בציטוט של הלקוח, ואז הציגו את עצמכם.',
  undefined,
  `שלושה עמודי תווך, פסקה לכל אחד.
עצרו לשאלות לפני המעבר.`,
];

export default [Cover, Agenda, Pillars];

Rules:

  • One entry per page, in the same order as the default export. A missing entry silently misaligns every note after it.
  • Multi-line notes work; use a template literal.
  • notes must be an ARRAY LITERAL. The dev server writes notes back into your source (PUT /__notes), and its parser rejects anything it cannot edit in place: a computed array, a spread, an imported constant, or a .map() all return 422 \notes` export is not an array literal` and permanently break editing notes from the drawer. Write the array out by hand.
  • Hebrew notes render LTR-aligned and you cannot fix it from the slide. The notes drawer and presenter pane set no dir, so Hebrew notes inherit the viewer chrome's direction. There is no author-side hook for this. Keep notes short and hard-wrap them yourself so the ragged alignment stays readable.
  • Never deliver a speech script as a separate file. If the user asks for "תסריט" (script) / "a script" / "speaker notes", it goes in this export.

Stable ships the feature without documentation. The notes runtime is present in 1.19.1, but none of the five skills bundled with that release documents it. The word "notes" does occur in them, in a table column header, a grid-overlay bullet and a line about inspector markers, but never in reference to this export. So sync:skills will not teach an agent the feature exists. That gap is the reason this section exists.

Dev-server surfaces worth knowing

These are viewer features, not authoring APIs, but users ask about them and an agent that knows they exist avoids rebuilding them by hand:

Surface What it does
Notes drawer / presenter view Renders the notes export above. Present mode shows notes beside the timer.
Command menu Keyboard-driven palette for jumping between slides and actions.
Presenter-window deck switching The presenter window can switch decks without dropping the presenter session.
Themes panel Renders each theme's <id>.demo.tsx. A theme with no demo file shows an empty card.
allowedHosts open-slide.config.ts accepts allowedHosts?: string[] | true, needed when serving the dev server through a tunnel or a non-localhost host.

Vertical budget reminder (the #1 source of broken slides)

Anything below 1080px on the canvas is silently cropped. Before writing any page, sum:

(font_size × line_height × number_of_lines) + gaps + (2 × padding) ≤ 1080

If you're tight, split into two pages. Never use overflow: auto/scroll/hidden, negative margins, or transforms to hide overflow. The canvas does not scroll; cropped content is gone. See ./rules/slide-authoring.md section "Vertical budget" for the full worked example.

For Hebrew text the budget is tighter than it looks: Hebrew renders ~10-15% wider per character than Latin at the same px size, so a heading that fits English on one line at 80px may wrap to two in Heebo. Either drop the size or shorten the copy. See hebrew-rtl.md for the calibration table.

Reference Links

Source URL What to Check
open-slide upstream repo https://github.com/1weiho/open-slide Latest API, breaking changes, version
open-slide docs / homepage https://open-slide.dev Install, demos
@open-slide/cli on npm https://www.npmjs.com/package/@open-slide/cli Current CLI version
@open-slide/core on npm https://www.npmjs.com/package/@open-slide/core Current runtime version, exports
MDN: CSS logical properties https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values RTL-aware CSS, inset-inline-start, padding-inline
MDN: <bdi> element https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdi Bidi isolation for mixed Hebrew+Latin
Google Fonts (Hebrew subset) https://fonts.google.com/?subset=hebrew Hebrew web fonts: Heebo, Rubik, Assistant, Noto Sans Hebrew
Upstream slide-id regex (slide-ops.ts) https://raw.githubusercontent.com/1weiho/open-slide/7fbd1ea84cf1ee7cd701cc9fe59bff3e4e0148c5/packages/core/src/editing/slide-ops.ts SLIDE_ID_RE, whether non-ASCII slide folder ids are still filtered out
Upstream webfont loading rules https://raw.githubusercontent.com/1weiho/open-slide/7fbd1ea84cf1ee7cd701cc9fe59bff3e4e0148c5/packages/core/skills/slide-authoring/references/webfonts.md The supported way to load a webfont (head injection, slide-keyed id)

Recommended MCP Servers

None directly, since open-slide has no API surface. The skill is purely client-side authoring guidance.

Gotchas

These are the failure modes a coding agent hits when authoring an open-slide deck in Hebrew without this skill. They are NOT user errors.

  1. Using paddingLeft and marginLeft everywhere. In LTR these read as "from the start of the line". In RTL they're literally on the wrong side of the canvas. Use paddingInlineStart / paddingInlineEnd (or shorthand paddingInline) so the layout flips automatically when dir="rtl" is set on the canvas root. The same applies to left / right (use insetInlineStart / insetInlineEnd) and text-align: left/right (use text-align: start/end).

  2. Setting direction: 'rtl' on <html> or globally. open-slide's canvas root is the page component, not the HTML document. The dev server chrome (file rail, navigation arrows) is upstream UI in English; flipping the document direction breaks it. Set dir="rtl" on the page component's root <div> only. The Inspector and presenter mode keep their LTR layout; only your slide content flips. There is no Hebrew option to switch them to either: OpenSlideConfig.locale is deprecated in favour of the slide UI's language switcher, and the shipped UI locales are en, zh-TW, zh-CN and ja only. Tell the user the authoring chrome stays English; only the deck is Hebrew.

  3. Naming the slide folder in Hebrew. It is the natural thing to do on a Hebrew deck and it silently loses the slide: discovery filters folder names through /^[a-z0-9_-]+$/i and only warns. Latin kebab-case id, Hebrew in meta.title. See "Slide folder ids must be ASCII" above.

  4. Loading the Hebrew webfont from inside the page component. A <style>@import ...</style> or a <link> rendered inside a Page re-registers the whole @font-face set once per mounted page, and the home page, the thumbnail rail, the overview grid and the PDF print root all mount every page at the same time. Upstream's rule is to inject the stylesheet once into <head> from module top level of index.tsx, with an element id keyed to the slide (osd-webfont-<slide-id>) so one slide's fonts cannot suppress another's. See hebrew-rtl.md section 3.

  5. The default display font is a serif with no Hebrew glyphs. defaultDesign.fonts.display is Georgia, "Times New Roman", serif and fonts.body is -apple-system, BlinkMacSystemFont, "Inter", system-ui, sans-serif. Georgia carries no Hebrew, so an unstyled Hebrew hero silently falls back to whatever system serif the OS picks (David / Times-like). That, not the body stack, is the real cause of the "my Hebrew deck renders in a dated serif" symptom. For decks that ship beyond a dev machine, load Heebo or Rubik via Google Fonts (css2, no subset parameter, it is ignored) and put them first in the stack with the system stack as fallback. See hebrew-rtl.md.

  6. Same type scale as English. 140-200px hero in Inter looks tight at the lower bound (140) for Hebrew, because Hebrew uppercase-equivalent (Hebrew has no case) sits at the cap-height-equivalent of large-x-height Latin fonts. Hebrew at 140px reads like Latin at 120px. Either bump the hero to 160-220px for Hebrew, or drop the line count.

  7. Mixing Hebrew and English without <bdi>. A bullet like "השתמשו ב-React Router" (use React Router) with mixed Hebrew + Latin tokens often renders the punctuation in the wrong place because the browser's bidi algorithm doesn't always isolate the Latin span. Wrap mixed runs in <bdi>: השתמשו ב-<bdi>React Router</bdi>. Critical for code identifiers, brand names, and URLs inside Hebrew copy.

  8. array.map for visually repeated cards. The upstream rule (one component instantiated per item, NOT map over a data array) applies in Hebrew too. The reason is the inspector: map shares one source location per item, so editing one Hebrew label changes all rendered cards. Define <Card /> once, instantiate it three times explicitly. See slide-authoring.md.

  9. Ignoring node_modules/.open-slide/current.json. When the user says the Hebrew phrase "תקן את הכותרת בעמוד הזה" ("fix the heading on this page"), the deictic "הזה" (this) maps exactly the same way the English "this" does, you must read current.json to find which slide they're on. Re-read it on every turn. See current-slide.md.

  10. Same vertical budget at the bumped Hebrew type scale. A cover that fits a 3-line LTR hero at 200px (3 × 200 × 0.95 = 570px) will overflow if you keep 3 lines AND bump to 232px for Hebrew (3 × 232 × 0.95 = 661px), once padding and subtitle are added you blow past 1080. Either stay at 1-2 lines for the Hebrew hero, or recompute the full budget with the bumped values before committing. Splitting is always the right answer.

  11. Mis-translated technical terms inside the slide copy. Translation services and well-meaning agents render English dev terminology into Hebrew that means something else. Common traps:

  • "themes" → "ערכאות" (Hebrew mistranslation) (literally "court instances", a legal term). Correct: "ערכות נושא" (Hebrew wording) (theme sets) or "תמות" (Hebrew transliteration) (transliteration).
  • "resolution" (as in "resolving a reference") → "רזולוציית" (Hebrew mistranslation) (means screen/image resolution). Correct: "פיענוח" (Hebrew correction) (decoding) or "זיהוי" (Hebrew correction) (identification).
  • "authoring" → "אוטרינג" (Hebrew spelling) (English transliteration, sounds techy and wrong). Correct: "כתיבה" (writing) or "יצירה" (creation).
  • "tuned scale" → "סקאלה מכוונת" (Hebrew wording) (literal but robotic). Better: "סקאלה מותאמת" (Hebrew wording) (adapted scale). Always verify Hebrew technical terms with a native speaker or a Hebrew dev community reference before shipping.
  1. Pairing brand colors as a gradient can produce an off-brand intermediate hue. A linear gradient from Israeli Blue (#003286) to YooTech Magenta (#bc46a2) blends through saturated purple, which is NOT in the agentskills.co.il palette. If a project has a brand palette, audit each combination: hold solid bold marks for primary brand colors, and only gradient between adjacent hues (e.g., Israeli Blue → Israeli Blue Light) or fade to neutral (cream / navy). One confident solid usually beats a contrived gradient.

  2. Audit your own copy for bidi traps. Recursively. A skill that warns about missing <bdi> will frequently MISS its own <bdi> calls inside Hebrew sentences ("השתמשו ב-RTL" (use RTL) needs <bdi>RTL</bdi>). When writing prose, slides, or examples, manually scan every Latin token embedded in Hebrew runs and wrap it. Code identifiers, brand names, version numbers, file extensions, all need isolation. The author writing the rule is not exempt from the rule.

Troubleshooting

Symptom Likely cause Fix
Hebrew text renders left-aligned Page root is missing dir="rtl" Add dir="rtl" to the page's outer <div>, or set direction: 'rtl' in its inline style
Hebrew heading renders in a serif when you wanted sans Verified cause: defaultDesign.fonts.display is Georgia, "Times New Roman", serif, and Georgia has no Hebrew glyphs, so the OS substitutes a system Hebrew serif Set fonts.display (not just fonts.body) to a Hebrew family and load it via head injection
Editing a note from the drawer fails, or the drawer stops saving The notes export is not an array literal, so the dev server's write-back returns 422 \notes` export is not an array literal. A computed array, a spread, an import or a .map()` all trigger it Write notes out as a plain array literal
Hebrew speaker notes are left-aligned in the presenter pane The notes drawer and presenter set no dir, so notes inherit the viewer chrome's direction. There is no author-side fix Keep notes short and hard-wrap them; this is viewer behaviour, not a bug in your slide
A signed number reads backwards on a Hebrew slide (-5 shows as 5-) The sign has no digit on its other side, so UAX#9 W4 cannot fold it into the number; it stays a neutral and jumps to the far side Wrap the signed number in <bdi>. Negative KPIs are the usual miss, they look like a single number
Brand name like "skills-il" appears with hyphen on the wrong side Mixed Hebrew+Latin without bidi isolation Wrap the Latin run in <bdi>
Hero heading wraps to a second line in Hebrew but not English Hebrew is wider per character Either bump hero size by ~15% or shorten the title
paddingLeft: 160 puts content against the right edge of an RTL slide Physical CSS in an RTL container Replace with paddingInline: 160 or paddingInlineStart: 160
Inspector arrows / file rail flipped You set direction: 'rtl' globally on <html> Move dir="rtl" to the page component's root only
A slide you created never appears in the sidebar or the build, but nothing errors The folder id under slides/ is not ASCII (a Hebrew name), so discovery skipped it with a warning only Rename the folder to Latin kebab-case; keep the Hebrew in meta.title. Grep the dev/build output for Ignoring slide folder
Hebrew webfont loads but the deck feels heavy on the home page or PDF export The @font-face set is registered once per mounted page because the stylesheet is injected inside a Page component Inject it once into <head> from module top level with an id keyed to the slide (osd-webfont-<slide-id>)
npm warn EBADENGINE Unsupported engine on npx @open-slide/cli init Node older than 22.12.0 (commander@15 requires >=22.12.0) Upgrade Node to 22.12 or newer
current.json does not exist Dev server has not been opened on a slide yet Run pnpm dev and open any slide in the browser, then re-read
Comment markers missing after applying Did not delete the marker line, only the text Re-run the regex; remove the entire line including trailing \n
@open-slide/cli init fails with "package not found" Network or registry issue Verify npm view @open-slide/cli version returns a version; clear pnpm/npm cache
Type imports fail Wrong package or stale install Imports come from @open-slide/core: import type { Page, SlideMeta, DesignSystem } from '@open-slide/core'

Bundled Resources

rules/
├── slide-authoring.md     # File contract, canvas, type scale, themes, design tokens, assets
├── create-slide.md        # New-deck workflow: theme picker, scoping questions, structure
├── create-theme.md        # Theme markdown authoring under themes/<id>.md
├── apply-comments.md      # @slide-comment marker handling
├── current-slide.md       # Resolving "this page" via current.json
└── hebrew-rtl.md          # Hebrew fonts, bidi, logical CSS, RTL DesignSystem (the new content)

Load on demand based on the task. Always start with hebrew-rtl.md for Hebrew or bilingual content.