feat(boundaries): add layouts-v2 zone so v2 shell layout can use components-v2
AD-G2 ("OrganizerLayoutV2 wraps AppShellV2") was in tension with AD-G5:
src/layouts/OrganizerLayoutV2.vue classified as the v1 `layouts` zone,
which is deliberately barred from components-v2. New `layouts-v2` zone
(src/layouts/*V2*.vue, mode:file) gets pages-v2-equivalent v2 capability;
the v1 `layouts` zone is unchanged so v2 isolation is preserved. RFC
AD-G5 amended; locked by 3 boundaries-v2.spec.ts regression tests (7/7).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -252,9 +252,16 @@ module.exports = {
|
||||
// (components-foundation) but NOT any other v1 component zone.
|
||||
// No v1 `from` rule lists components-v2/pages-v2 → back-porting
|
||||
// is structurally impossible (RFC-WS-GUI-REDESIGN AD-G5).
|
||||
// layouts-v2 (src/layouts/*V2*.vue, e.g. OrganizerLayoutV2) is
|
||||
// the v2 shell-composition zone: SAME v2 capability as pages-v2
|
||||
// (may import components-v2 + navigation) so AD-G2's
|
||||
// "OrganizerLayoutV2 wraps AppShellV2" holds, WITHOUT widening
|
||||
// the v1 `layouts` zone (still cannot reach components-v2 →
|
||||
// AD-G5 isolation intact). Locked by tests/unit/boundaries-v2.spec.ts.
|
||||
{ from: 'components-foundation', allow: ['types', 'utils', 'lib', 'composables', 'composables-forms', 'stores', 'components-foundation'] },
|
||||
{ from: 'components-v2', allow: ['types', 'utils', 'lib', 'composables', 'composables-forms', 'stores', 'components-v2', 'components-foundation'] },
|
||||
{ from: 'pages-v2', allow: ['types', 'utils', 'lib', 'composables', 'composables-forms', 'stores', 'navigation', 'components-v2', 'components-foundation', 'layouts', 'plugins'] },
|
||||
{ from: 'layouts-v2', allow: ['types', 'utils', 'lib', 'composables', 'composables-forms', 'stores', 'navigation', 'components-v2', 'components-foundation', 'layouts', 'plugins'] },
|
||||
{ from: 'components', allow: ['types', 'utils', 'lib', 'composables', 'composables-forms', 'stores', 'components', 'components-shared', 'components-organizer'] },
|
||||
{ from: 'layouts', allow: ['types', 'utils', 'lib', 'composables', 'composables-forms', 'stores', 'stores-portal', 'navigation', 'components', 'components-shared', 'components-portal', 'components-organizer', 'layouts'] },
|
||||
|
||||
@@ -315,6 +322,14 @@ module.exports = {
|
||||
{ type: 'components-foundation', pattern: 'src/components/Icon.vue', mode: 'file' },
|
||||
{ type: 'components-v2', pattern: 'src/components-v2/**' },
|
||||
{ type: 'components', pattern: 'src/components/**' },
|
||||
// layouts-v2 MUST precede the generic `layouts` element: first
|
||||
// match wins. The single `*` does not cross `/`, so this matches
|
||||
// only top-level v2 layout files (src/layouts/OrganizerLayoutV2.vue)
|
||||
// and NOT src/layouts/components/AppShellV2.vue (subdir → stays
|
||||
// `layouts`, which is correct: AppShellV2 imports only stores).
|
||||
// mode:'file' is REQUIRED for a file-glob element (same reason as
|
||||
// the Icon.vue bridge above) — RFC AD-G5 / boundaries-v2.spec.ts.
|
||||
{ type: 'layouts-v2', pattern: 'src/layouts/*V2*.vue', mode: 'file' },
|
||||
{ type: 'layouts', pattern: 'src/layouts/**' },
|
||||
{ type: 'pages-register', pattern: 'src/pages/register/**' },
|
||||
{ type: 'pages-portal', pattern: 'src/pages/portal/**' },
|
||||
|
||||
@@ -67,4 +67,38 @@ describe('boundaries — v2 zones', () => {
|
||||
|
||||
expect(errs.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// layouts-v2 zone (RFC AD-G5): the v2 shell layout (src/layouts/*V2*.vue)
|
||||
// gets pages-v2-equivalent v2 capability so AD-G2 ("OrganizerLayoutV2
|
||||
// wraps AppShellV2") holds — WITHOUT widening the v1 `layouts` zone.
|
||||
// These lock both halves: the new edge AND the preserved isolation.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
it('allows layouts-v2 → components-v2 (AD-G5: v2 shell composition)', async () => {
|
||||
const errs = await boundaryErrors(
|
||||
'src/layouts/OrganizerLayoutV2.vue',
|
||||
'<script setup lang="ts">import AppSidebar from \'@/components-v2/layout/AppSidebar.vue\'</script><template><AppSidebar /></template>',
|
||||
)
|
||||
|
||||
expect(errs).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('allows layouts-v2 → navigation (v2 layout sources nav data)', async () => {
|
||||
const errs = await boundaryErrors(
|
||||
'src/layouts/OrganizerLayoutV2.vue',
|
||||
'<script setup lang="ts">import { orgNavItems } from \'@/navigation/vertical\'</script><template><div>{{ orgNavItems.length }}</div></template>',
|
||||
)
|
||||
|
||||
expect(errs).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('forbids v1 layouts → components-v2 (AD-G5 isolation preserved)', async () => {
|
||||
const errs = await boundaryErrors(
|
||||
'src/layouts/OrganizerLayout.vue',
|
||||
'<script setup lang="ts">import AppSidebar from \'@/components-v2/layout/AppSidebar.vue\'</script><template><AppSidebar /></template>',
|
||||
)
|
||||
|
||||
expect(errs.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -30,9 +30,19 @@ Tailwind + FormField + DataTable conventions) remain binding.
|
||||
`useAuthStore`/`useOrganisationStore`. One new `useShellUiStore` holds
|
||||
only sidebar/theme/density + right-drawer state. `provide`/`inject`
|
||||
from crewli-starter is replaced per-port (no `inject()` survives).
|
||||
- **AD-G5 — Boundaries.** New `components-v2`/`pages-v2` zones; the only
|
||||
v1→v2 bridge is a narrow `components-foundation` zone (FormField,
|
||||
Icon). No back-porting (structurally enforced).
|
||||
- **AD-G5 — Boundaries.** New `components-v2`/`pages-v2`/`layouts-v2`
|
||||
zones; the only v1→v2 bridge is a narrow `components-foundation` zone
|
||||
(FormField, Icon). No back-porting (structurally enforced). The v2
|
||||
shell layout (`src/layouts/*V2*.vue`, e.g. `OrganizerLayoutV2`) is the
|
||||
`layouts-v2` zone — same v2 capability as `pages-v2` (may import
|
||||
`components-v2` + `navigation`) so AD-G2's "`OrganizerLayoutV2` wraps
|
||||
`AppShellV2`" holds. The v1 `layouts` zone is unchanged and still
|
||||
cannot import `components-v2`, so v2 isolation is preserved (only
|
||||
top-level `*V2*.vue` layout files gain v2 capability;
|
||||
`src/layouts/components/AppShellV2.vue` stays in `layouts` since it
|
||||
imports only `stores`). Locked by `tests/unit/boundaries-v2.spec.ts`
|
||||
(`layouts-v2 → components-v2` allowed; v1 `layouts → components-v2`
|
||||
forbidden).
|
||||
- **AD-G6 — Testing.** TEST-INFRA-001 (✅ Resolved) Playwright-CT +
|
||||
visual foundation is kept as the CI gate; Storybook a11y is
|
||||
complementary. v2 visual baselines are captured from the v2 component
|
||||
|
||||
Reference in New Issue
Block a user