- Remove ScrubInput aliases, app wrappers, test hooks, and translated legacy references - Share a Tailwind NumberField demo between Storybook and VitePress - Generate component API tables from Vue metadata and type-check examples with Twoslash - Upgrade the docs stack to VitePress 2
138 lines
8.2 KiB
Markdown
138 lines
8.2 KiB
Markdown
# Contributing
|
|
|
|
## Setup
|
|
|
|
```bash
|
|
git clone https://github.com/open-pencil/open-pencil.git
|
|
cd open-pencil
|
|
git clone https://github.com/open-pencil/vue-stream-markdown.git
|
|
bun install
|
|
```
|
|
|
|
## Development
|
|
|
|
```bash
|
|
bun run dev # Vite dev server on localhost:1420
|
|
bun run tauri dev # Tauri desktop app with hot reload
|
|
|
|
# For macOS release builds with ad-hoc signing (no Apple Developer account, local testing only):
|
|
APPLE_SIGNING_IDENTITY=- bun run tauri build -c '{"bundle": { "createUpdaterArtifacts": false }}'
|
|
```
|
|
|
|
## Pull requests
|
|
|
|
Pull requests must be reviewable without guessing the author's intent.
|
|
|
|
### PR title
|
|
|
|
- Write the title in English.
|
|
- Be specific about the actual change; avoid vague titles such as `fix`, `update`, `some fixes`, `changes`, or `WIP`.
|
|
- Use Conventional Commits when it fits the change, for example `fix: handle empty exports` or `docs: clarify CLI setup`.
|
|
|
|
### PR body
|
|
|
|
- Follow the PR template when one is provided.
|
|
- Explain what changed and why it changed.
|
|
- Include a concrete list or paragraph of meaningful changes.
|
|
- Document validation, such as `bun run check`, targeted tests, docs-only review, or an explicit reason validation was not run.
|
|
- Keep the body primarily in English. Code identifiers, file paths, logs, error messages, and short quoted examples may use their original language.
|
|
|
|
### Reviewability
|
|
|
|
Do not submit placeholder PRs. Remove template comments before opening a PR. Do not leave dangling issue references such as `Fixes #`, `TODO`, `TBD`, empty headings, unfilled sections, or similar unfinished text.
|
|
|
|
CodeRabbit may flag PR description or readability issues for maintainers to review. Missing template sections or validation details are normal review feedback; they are not, by themselves, a personal judgment on the contributor. Maintainers may close PRs manually when they are clearly automated, not written in English, unrelated to the project, or impossible to review without substantial guesswork. If you are unsure how to fix something, please open a detailed issue instead of submitting a placeholder PR.
|
|
|
|
## Quality checks
|
|
|
|
Run all of these before submitting a PR:
|
|
|
|
```bash
|
|
bun run check # oxlint + typecheck
|
|
bun run format # oxfmt with import sorting
|
|
bun run test:dupes # jscpd < 3% duplication
|
|
bun run test:unit # bun:test (tests/engine/)
|
|
bun run test # Playwright E2E (auto-starts dev server)
|
|
```
|
|
|
|
## Project structure
|
|
|
|
- `packages/core` — scene graph, renderer, layout, codec (zero DOM deps)
|
|
- `packages/cli` — headless CLI for .fig inspection and export
|
|
- `packages/mcp` — MCP server for AI tools (stdio + HTTP)
|
|
- `packages/docs` — VitePress documentation site (openpencil.dev)
|
|
- `src/` — Tauri/Vite desktop editor
|
|
|
|
## Conventions
|
|
|
|
See [`AGENTS.md`](./AGENTS.md) for the full architecture reference, code conventions, and quality checklist. Key points:
|
|
|
|
- Bun runtime, not Node
|
|
- Tailwind 4 for styles, no inline CSS or `<style>` blocks
|
|
- No `any`, no `!` non-null assertions
|
|
- `@/` import alias for app code, relative imports within core
|
|
- Use `crypto.getRandomValues()`, never `Math.random()`
|
|
- Icons via unplugin-icons (`<icon-lucide-*>`)
|
|
- Use existing deps and Reka UI components before hand-rolling (see AGENTS.md → Code quality)
|
|
- Follow the Reka UI-inspired file structure: PascalCase component namespace folders and Vue files, lowercase/kebab non-component domains, and multi-file root components colocated inside their namespace folder
|
|
- Keep UI labels translatable. Do not hardcode user-facing strings in Vue templates when an i18n namespace exists.
|
|
- Keep shortcuts out of labels/translations. Put command shortcut tokens and keyboard bindings in `packages/vue/src/editor/commands/registry.ts`, then render them with `formatShortcut()` so macOS and Windows/Linux display correctly.
|
|
- Canvas context-menu grouping belongs in `packages/vue/src/editor/menu-model/canvas.ts`; components should render the menu model instead of hand-building command groups.
|
|
|
|
## Test IDs (`data-test-id`)
|
|
|
|
Every interactive or structurally significant element must have a `data-test-id` attribute. These are used by Playwright E2E tests and must follow the naming convention below.
|
|
|
|
### Naming rules
|
|
|
|
- **kebab-case**, all lowercase
|
|
- Pattern: `{component}-{element}` or `{component}-{element}-{variant}`
|
|
- Mobile counterparts are prefixed with `mobile-`
|
|
- Dynamic IDs use template literals: `` :data-test-id="`toolbar-tool-${key.toLowerCase()}`" ``
|
|
|
|
### Nomenclature
|
|
|
|
| Prefix | Component | Examples |
|
|
|--------|-----------|---------|
|
|
| `toolbar-` | Desktop toolbar | `toolbar`, `toolbar-tool-select`, `toolbar-flyout-frame`, `toolbar-flyout-item-ellipse` |
|
|
| `mobile-toolbar-` | Mobile toolbar | `mobile-toolbar`, `mobile-toolbar-prev`, `mobile-toolbar-next`, `mobile-toolbar-tool-select`, `mobile-toolbar-flyout-frame`, `mobile-toolbar-copy`, `mobile-toolbar-front` |
|
|
| `mobile-toolbar-tools` | Mobile tools category | Container for drawing tools |
|
|
| `mobile-toolbar-edit` | Mobile edit category | Container for edit actions (copy, paste, cut, duplicate, delete) |
|
|
| `mobile-toolbar-arrange` | Mobile arrange category | Container for arrange actions (front, back, group, ungroup, lock) |
|
|
| `mobile-drawer-` | Mobile bottom drawer | `mobile-drawer`, `mobile-drawer-handle`, `mobile-drawer-pages`, `mobile-drawer-content`, `mobile-drawer-layers`, `mobile-drawer-design`, `mobile-drawer-code`, `mobile-drawer-ai` |
|
|
| `mobile-ribbon-` | Mobile bottom tab bar | `mobile-ribbon`, `mobile-ribbon-layers`, `mobile-ribbon-design`, `mobile-ribbon-code`, `mobile-ribbon-ai` |
|
|
| `layers-` | Layers panel | `layers-panel`, `layers-header`, `layers-tree`, `layers-item` |
|
|
| `pages-` | Pages panel | `pages-panel`, `pages-header`, `pages-item`, `pages-item-input`, `pages-add` |
|
|
| `properties-` | Properties panel | `properties-panel`, `properties-tab-design`, `properties-tab-code`, `properties-tab-ai`, `properties-zoom` |
|
|
| `design-` | Design tab | `design-node-header`, `design-multi-header`, `design-panel-single`, `design-panel-multi`, `design-panel-empty` |
|
|
| `position-` | Position section | `position-section`, `position-align-left`, `position-flip-horizontal`, `position-rotate-90` |
|
|
| `layout-` | Layout section | `layout-section`, `layout-add-auto`, `layout-remove-auto`, `layout-direction-horizontal` |
|
|
| `fill-` | Fill section | `fill-section`, `fill-section-add`, `fill-item` |
|
|
| `stroke-` | Stroke section | `stroke-section`, `stroke-section-add`, `stroke-item` |
|
|
| `effects-` | Effects section | `effects-section`, `effects-section-add`, `effects-item` |
|
|
| `export-` | Export section | `export-section`, `export-section-add`, `export-button`, `export-item` |
|
|
| `typography-` | Typography section | `typography-section`, `typography-missing-font` |
|
|
| `variables-` | Variables | `variables-section`, `variables-dialog`, `variables-add-variable` |
|
|
| `context-` | Context menu | `context-copy`, `context-paste`, `context-delete`, `context-group` |
|
|
| `color-` | Color picker | `color-picker-popover`, `color-picker-swatch`, `color-hex-input` |
|
|
| `fill-picker-` | Fill picker | `fill-picker-swatch`, `fill-picker-tab-solid`, `fill-picker-tab-gradient` |
|
|
| `font-picker-` | Font picker | `font-picker-trigger`, `font-picker-search`, `font-picker-item` |
|
|
| `chat-` | Chat / AI panel | `chat-panel`, `chat-input`, `chat-send-button`, `chat-messages` |
|
|
| `code-` | Code panel | `code-panel`, `code-panel-header`, `code-panel-copy` |
|
|
| `collab-` | Collaboration | `collab-popover`, `collab-share-button`, `collab-copy-link` |
|
|
| `canvas-` | Canvas | `canvas-area`, `canvas-element`, `canvas-loading` |
|
|
| `editor-` | Editor root | `editor-root`, `editor-document-name`, `editor-show-ui` |
|
|
| `app-` | App chrome | `app-logo`, `app-document-name`, `app-toggle-ui`, `app-select-trigger` |
|
|
| `tabbar-` | Tab bar | `tabbar-tab`, `tabbar-new`, `tabbar-close` |
|
|
| `number-field` | Number field | `number-field`, `number-field-input` |
|
|
| `toast-` | Toast notifications | `toast-item`, `toast-close`, `toast-copy-error` |
|
|
| `safari-banner` | Safari warning | `safari-banner`, `safari-banner-dismiss` |
|
|
|
|
## Test fixtures
|
|
|
|
`.fig` fixtures in `tests/fixtures/` are Git LFS. Use `git push --no-verify` to skip the slow LFS pre-push hook unless you changed `.fig` files.
|
|
|
|
## Commits
|
|
|
|
Follow the existing style in `git log`. Keep messages concise. Update `CHANGELOG.md` for user-facing changes.
|