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:
2026-05-17 02:03:00 +02:00
parent 6e5c5bbec3
commit a341a60412
3 changed files with 62 additions and 3 deletions

View File

@@ -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/**' },

View File

@@ -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)
})
})