chore: enforce Vue style and state boundaries
This commit is contained in:
parent
3e6093106c
commit
686ca5326f
|
|
@ -238,7 +238,7 @@ Release commits are the exception: keep using `Release v0.x.y`.
|
|||
## Code conventions
|
||||
|
||||
- Do not place code or tests ad hoc. Before adding or moving files, inspect the existing folder structure and nearby patterns, then put changes in the established domain-specific location. If no proper location exists, create one deliberately and update docs/conventions as needed.
|
||||
- Architecture boundaries are enforced by Steiger (`bun run check:arch`). App code must use public workspace package exports, workspace packages must not import app `src/` code, package-local aliases (`#core`, `#vue`, `#cli`, `#mcp`) are only for their owning package, core must stay framework-agnostic, app service/domain code (`src/app/**`) must not import app component/view layers, components must not import views, shared UI (`src/components/ui/**`) must not import app services/stores, property-panel internals must stay inside the property panel, canvas/editor overlay code must not import property-panel internals, committed code must not import scratch/generated/vendor internals, and durable docs belong under `packages/docs/**` unless the root Markdown allowlist is deliberately updated.
|
||||
- Architecture boundaries are enforced by Steiger (`bun run check:arch`). App code must use public workspace package exports, workspace packages must not import app `src/` code, package-local aliases (`#core`, `#vue`, `#cli`, `#mcp`) are only for their owning package, core must stay framework-agnostic, app service/domain code (`src/app/**`) must not import app component/view layers, components must not import views, shared UI (`src/components/ui/**`) must not import app services/stores, property-panel internals must stay inside the property panel, canvas/editor overlay code must not import property-panel internals, Vue components must not use `<style>` blocks, code outside core editor internals must not assign `editor.state.selectedIds` or `editor.state.activeTool` directly, committed code must not import scratch/generated/vendor internals, and durable docs belong under `packages/docs/**` unless the root Markdown allowlist is deliberately updated.
|
||||
- Test placement is strict and enforced by Steiger: app E2E tests live under `tests/e2e/**` and use `*.spec.ts`; Figma automation tests live under `tests/figma/**` and use `*.spec.ts`; engine/unit tests live under `tests/engine/**` and use `*.test.ts` (with `helpers.ts`, `*.bench.ts`, and `visual-*` support scripts allowed); shared test utilities live under `tests/helpers/**`. Do not commit temporary/profile specs (`*.tmp.*`, `*.profile.*`). Do not put store-only/internal-state assertions in E2E. If a test drives the UI like a user and verifies visible behavior, it can be E2E; if it creates nodes through internals and asserts graph state, it belongs in engine/unit coverage.
|
||||
|
||||
### File and folder naming
|
||||
|
|
|
|||
|
|
@ -230,6 +230,27 @@ const noE2EImportsInEngineTests = createImportRule(
|
|||
}
|
||||
)
|
||||
|
||||
const noVueStyleBlocks = createFileRule('open-pencil/no-vue-style-blocks', (sourceRel) => {
|
||||
if (!sourceRel.endsWith('.vue')) return null
|
||||
if (!sourceRel.startsWith('src/') && !sourceRel.startsWith('packages/vue/src/')) return null
|
||||
const content = readFileSync(path.resolve(sourceRel), 'utf8')
|
||||
return /<style\b/i.test(content)
|
||||
? 'Vue components must not use <style> blocks. Use Tailwind utilities or global app.css tokens.'
|
||||
: null
|
||||
})
|
||||
|
||||
const noDirectSelectionOrToolStateMutation = createFileRule(
|
||||
'open-pencil/no-direct-selection-tool-state-mutation',
|
||||
(sourceRel) => {
|
||||
if (!sourceRel.endsWith('.ts') && !sourceRel.endsWith('.vue')) return null
|
||||
if (sourceRel.startsWith('packages/core/src/editor/')) return null
|
||||
const content = readFileSync(path.resolve(sourceRel), 'utf8')
|
||||
return /\.state\.(selectedIds|activeTool)\s*=(?!=)/.test(content)
|
||||
? 'Do not assign editor.state.selectedIds or editor.state.activeTool directly. Use editor selection/tool actions.'
|
||||
: null
|
||||
}
|
||||
)
|
||||
|
||||
const noRootMarkdownClutter = createFileRule('open-pencil/no-root-markdown-clutter', (sourceRel) => {
|
||||
if (sourceRel.includes('/')) return null
|
||||
if (!sourceRel.endsWith('.md')) return null
|
||||
|
|
@ -397,6 +418,8 @@ export const openPencilArchitecturePlugin = {
|
|||
meta: { name: 'open-pencil-architecture', version: '0.0.0' },
|
||||
ruleDefinitions: [
|
||||
preferDomainFoldersOverFilenamePrefixes,
|
||||
noVueStyleBlocks,
|
||||
noDirectSelectionOrToolStateMutation,
|
||||
strictTestFilePlacement,
|
||||
noEngineOnlyAssertionsInE2E,
|
||||
noE2EImportsInEngineTests,
|
||||
|
|
|
|||
26
src/app.css
26
src/app.css
|
|
@ -109,6 +109,32 @@ body {
|
|||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.code-highlight .token.tag {
|
||||
color: var(--color-code-tag);
|
||||
}
|
||||
|
||||
.code-highlight .token.attr-name {
|
||||
color: var(--color-code-attribute);
|
||||
}
|
||||
|
||||
.code-highlight .token.attr-value,
|
||||
.code-highlight .token.string {
|
||||
color: var(--color-code-string);
|
||||
}
|
||||
|
||||
.code-highlight .token.number,
|
||||
.code-highlight .token.boolean {
|
||||
color: var(--color-code-number);
|
||||
}
|
||||
|
||||
.code-highlight .token.punctuation {
|
||||
color: var(--color-code-punctuation);
|
||||
}
|
||||
|
||||
.code-highlight .token.keyword {
|
||||
color: var(--color-code-attribute);
|
||||
}
|
||||
|
||||
input[type='number'] {
|
||||
appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ function copyReference() {
|
|||
</div>
|
||||
|
||||
<ScrollAreaRoot class="min-h-0 flex-1">
|
||||
<ScrollAreaViewport class="size-full">
|
||||
<ScrollAreaViewport class="code-highlight size-full">
|
||||
<div class="p-3">
|
||||
<div v-for="(html, i) in highlightedLines" :key="i" class="flex text-xs leading-5">
|
||||
<span
|
||||
|
|
@ -113,27 +113,3 @@ function copyReference() {
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.token.tag {
|
||||
color: var(--color-code-tag);
|
||||
}
|
||||
.token.attr-name {
|
||||
color: var(--color-code-attribute);
|
||||
}
|
||||
.token.attr-value,
|
||||
.token.string {
|
||||
color: var(--color-code-string);
|
||||
}
|
||||
.token.number {
|
||||
color: var(--color-code-number);
|
||||
}
|
||||
.token.punctuation {
|
||||
color: var(--color-code-punctuation);
|
||||
}
|
||||
.token.boolean {
|
||||
color: var(--color-code-number);
|
||||
}
|
||||
.token.keyword {
|
||||
color: var(--color-code-attribute);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ export default defineConfig([
|
|||
{
|
||||
rules: {
|
||||
'open-pencil/prefer-domain-folders-over-filename-prefixes': 'error',
|
||||
'open-pencil/no-vue-style-blocks': 'error',
|
||||
'open-pencil/no-direct-selection-tool-state-mutation': 'error',
|
||||
'open-pencil/strict-test-file-placement': 'error',
|
||||
'open-pencil/no-engine-only-assertions-in-e2e': 'error',
|
||||
'open-pencil/no-e2e-imports-in-engine-tests': 'error',
|
||||
|
|
|
|||
|
|
@ -39,8 +39,7 @@ async function selectDemoCard(page: Parameters<typeof test>[0]['page'], canvas:
|
|||
.map((n) => `${n.name}:${n.type}`)
|
||||
.join(', ')}`
|
||||
)
|
||||
store.state.selectedIds = new Set([card.id])
|
||||
store.requestRender()
|
||||
store.select([card.id])
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
|
||||
|
|
|
|||
|
|
@ -103,8 +103,7 @@ test('stroke picker hsb saturation and brightness sliders update stroke color on
|
|||
align: 'INSIDE'
|
||||
}
|
||||
store.updateNodeWithUndo(card.id, { strokes: [stroke] }, 'Add demo card stroke')
|
||||
store.state.selectedIds = new Set([card.id])
|
||||
store.requestRender()
|
||||
store.select([card.id])
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue