Materializr
Open-source parametric 3D CAD for makers — constraint sketches, solid modeling, threads, SVG & text engraving, STL/STEP export. Android, iPad, Linux, Mac, Windows. Vibe coded.
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
A solo-drawing clone of Excalidraw built with TypeScript, React 19, Next.js 16, and TurboRepo. v1 is single-user only — no collaboration, no accounts.
Scope decision and full feature inventory live in docs/superpowers/exploration/.
The implementation plan and design spec will live under docs/superpowers/specs/.查看完整 README ↗A solo-drawing clone of Excalidraw built with TypeScript, React 19, Next.js 16, and TurboRepo. v1 is single-user only — no collaboration, no accounts.
Scope decision and full feature inventory live in
docs/superpowers/exploration/. The implementation plan and design spec will live underdocs/superpowers/specs/.
| Layer | Choice | Notes |
|---|---|---|
| Runtime / framework | Next.js 16 + React 19 | App Router. Static export supported. |
| Language | TypeScript | Strict mode across all packages. |
| Monorepo | TurboRepo + pnpm workspaces | Standard for the toolchain. |
| Styling | Tailwind CSS v4 | First-class Next 16 integration. |
| Drawing engine | rough.js | Non-negotiable for the hand-drawn look. |
| State | Vanilla store + tool FSMs + Zustand | See State Management below. |
| Persistence | localStorage + IndexedDB (idb) | Scene in localStorage; image binaries in IndexedDB. |
| File format | .excalidraw JSON | Same shape as upstream → files interop with the real Excalidraw. |
| i18n | i18next | English + Korean only for v1. |
| Tests | Vitest (unit, per package) + Playwright (e2e in apps/web) | |
| Deploy | Vercel (primary) + static export for self-hosting |
apps/
web/ ← Next.js 16 app (UI shell, routes, fonts, i18next bootstrap)
packages/
scene/ ← scene model + history (the source of truth for elements on canvas)
geometry/ ← math helpers, hit-tests, transforms
renderer/ ← rough.js + canvas drawing pipeline
tools/ ← per-tool interaction logic (rectangle, ellipse, draw, text, …)
ui/ ← React components (toolbar, properties panel, dialogs, command palette)
persistence/ ← localStorage + IndexedDB + .excalidraw file I/O
We considered three layouts — minimal (2 packages), layered (4), and domain-split (6). We picked the domain-split version because:
@excalidraw/element, @excalidraw/math, @excalidraw/utils etc.). When we need to figure out how the real app does something, the mental map already lines up.geometry is pure math, scene knows nothing about how things are drawn, renderer knows nothing about pointer events, tools translate user intent into scene mutations, ui is React-only chrome, persistence is storage I/O. Each package answers exactly one question about the system.renderer consumes scene but is consumed only by apps/web, we can add an SVG renderer for export without touching anything else. Same for a future server-side PNG renderer.dev; we pay the structural cost only at refactor time.The trade-off vs. the layered (4-package) option: more package.json files and more import-path discipline. We accepted that cost in exchange for the clean boundary between scene (data model), geometry (math), renderer (drawing), and tools (interaction). Bundling those together would create a "core" package that grows into a god-module.
An Excalidraw-style app has three different kinds of state, each best handled differently. Putting them all in one library is a mistake — the performance characteristics, lifecycle, and consumers are different.
packages/scene)What it holds: the actual elements on the canvas. The source of truth for "what's drawn."
Implementation: a vanilla TypeScript Store class with a pub/sub interface. No React, no state library.
Why:
Scene class).packages/tools)What it holds: per-tool interaction state — idle → drawing → finishing, idle → dragging → resizing, etc., driven by pointer/keyboard events.
Implementation: hand-rolled discriminated-union state machines, one per tool. Each tool exports a reducer (state, event) → state plus a commit hook that mutates the scene.
Why not XState:
apps/web)What it holds: active tool, current theme, dialog open/closed flags, library panel toggle, zen mode, view mode, grid on/off, snap-to-objects on/off, command-palette open, i18n locale.
Implementation: Zustand with the slice pattern (one slice per concern: useToolStore, useThemeStore, useDialogStore, …).
Why Zustand:
useSyncExternalStore.Why not Jotai (which upstream Excalidraw uses): for our scope, the atomic model is overkill. We have ~10 chrome flags, not a sprawling derived-atom graph.
Why not Redux Toolkit: too much ceremony — action types, slices, reducers, immer middleware — for ~10 globals.
Why not useReducer + Context: every Context update re-renders every consumer in the subtree. Zustand's selector subscriptions don't.
┌─ packages/scene ─────────────┐ vanilla Store (pub/sub)
│ Element[], history stack │ ← single source of truth
└──────────────────────────────┘
▲
│ subscribe() — renderer reads directly, no React
│
┌─ packages/tools ─────────────┐ discriminated-union state machines
│ RectangleTool, TextTool, … │ ← pointer/keyboard events in
└──────────────────────────────┘ scene mutations out
▲
│ commits to scene
│
┌─ apps/web (Zustand) ─────────┐ React-friendly chrome state
│ active tool, theme, modals │ ← what the UI cares about
└──────────────────────────────┘
The arrows are one-way: UI tells tools which is active; tools commit to scene; renderer reads scene. The scene never "asks" the UI anything — it just emits change events.
This README captures decisions made during initial brainstorming on 2026-04-28. The next deliverables:
docs/superpowers/specs/2026-04-28-excalidraw-clone-design.mdSee docs/superpowers/exploration/v1-scope-decision.md for the full in/out/deferred feature list.