Skip to content

Commit 9ecf9d9

Browse files
alicodingclaude
andauthored
fix: de-seed the reference-architecture example -- capabilities ship, concepts don't (#236)
* fix: de-seed the reference-architecture example -- capabilities ship, concepts don't (ADR-0041 update) Owner correction from live beta dogfooding: Current/Interim/Target perspectives and the System landscape cards were a modeling concept hardcoded as product content. The Perspective capability stays; BuiltInPerspectives() returns nil, the landscape cards/links retire via seed tombstones (retireGoneSeedsLocked -- user-modified copies survive, contained cards promote to the parent, dangling links sweep), and existing installs converge on next reconcile. Compare's proof moves to a user-authored pair in atlas-perspectives.spec.ts; the MCP perspective-param tests author their own fixture through public service surfaces. Fingerprints regenerated; census assertions in the projections/select-group specs follow the smaller seed set. Claude-Session: https://claude.ai/code/session_01FW5GkkAG8du7tNdYLk2zSd Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: atlas-authoring coverage census follows the de-seeded set (1/3 linked, 0/3 mirrored) Claude-Session: https://claude.ai/code/session_01FW5GkkAG8du7tNdYLk2zSd Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 813fdac commit 9ecf9d9

9 files changed

Lines changed: 395 additions & 244 deletions

frontend/e2e/atlas-authoring.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,15 @@ test('atlas creation core: tray, placement popover, right-click create, sticky n
8181
await expect(page.getByTestId('atlas-jump-no-matches')).toBeVisible()
8282
await page.keyboard.press('Escape')
8383

84-
// "My space" seeds four children (goal 0095 slice 3 added "System
85-
// landscape" alongside Getting started/Example area/Scratchpad) --
86-
// a hand-countable 1/4 linked, 0/4 mirrored (same census
87-
// atlas-projections.spec.ts's own coverage test pins).
84+
// "My space" seeds three children (Getting started / Example area /
85+
// Scratchpad; the reference-architecture landscape was de-seeded,
86+
// ADR-0041's Update) -- a hand-countable 1/3 linked, 0/3 mirrored
87+
// (same census atlas-projections.spec.ts's own coverage test pins).
8888
await page.getByTestId('atlas-open-coverage').click()
8989
const coverageDialog = page.locator('[data-component="atlas-coverage-dialog"]')
9090
await expect(coverageDialog).toBeVisible()
91-
await expect(coverageDialog.getByTestId('atlas-coverage-link-value')).toHaveText('1/4 linked')
92-
await expect(coverageDialog.getByTestId('atlas-coverage-mirror-value')).toHaveText('0/4 mirrored')
91+
await expect(coverageDialog.getByTestId('atlas-coverage-link-value')).toHaveText('1/3 linked')
92+
await expect(coverageDialog.getByTestId('atlas-coverage-mirror-value')).toHaveText('0/3 mirrored')
9393
await page.keyboard.press('Escape')
9494
await expect(coverageDialog).not.toBeVisible()
9595

frontend/e2e/atlas-perspectives.spec.ts

Lines changed: 59 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ import { ATLAS_KIND_TOPIC, selectKind } from './fixtures/kindPicker'
2828
// Runs against the seeded "My space" tree (internal/domain/atlas/
2929
// builtin.go): My space (root) holds Getting started, Scratchpad,
3030
// Example area (which holds Ada Lovelace and Project charter), and
31-
// System landscape (which holds Web app/Data store/Sync service, the
32-
// seeded Current/Interim/Target perspectives' own scope); a seeded
31+
// perspectives are user-authored (none ship seeded); each test
32+
// builds what it asserts.
3333
// link connects Getting started -> Ada Lovelace.
3434

3535
async function withServer(testInfo: { parallelIndex: number }, run: (page: Awaited<ReturnType<import('@playwright/test').Browser['newPage']>>) => Promise<void>): Promise<void> {
@@ -244,61 +244,76 @@ test('membership removes via the board context menu, and deleting a perspective
244244
})
245245
})
246246

247-
// The seeded reference-architecture example (goal 0095 slice 3,
248-
// internal/domain/atlas/builtin.go's BuiltInPerspectives): a "System
249-
// landscape" card (a direct child of "My space", never disturbing the
250-
// pre-existing top-level census other specs pin -- e.g.
251-
// atlas-projections.spec.ts's coverage stat) holding "Web app"/"Data
252-
// store"/"Sync service", with three seeded perspectives -- "Current"
253-
// (app + data store, wired directly), "Interim" (adds the sync
254-
// service alongside the old link), "Target" (the old direct link is
255-
// gone, only the new shape remains).
247+
// Compare is proven over a USER-authored pair of perspectives -- no
248+
// perspective ships seeded (concepts are the user's; only the
249+
// capability ships). Link-level diff semantics stay proven at the Go
250+
// layer (DiffPerspectives' own tests); this flow pins the UI: author,
251+
// diff, and the empty-group omission.
256252
// eslint-disable-next-line no-empty-pattern -- this test needs `testInfo` (the second arg), not any fixture.
257-
test('the seeded reference-architecture example renders with no regression to the default view, and Compare shows the Current -> Target diff', async ({}, testInfo) => {
253+
test('Compare shows the diff between two user-authored perspectives', async ({}, testInfo) => {
258254
await withServer(testInfo, async (page) => {
259-
// No regression: every pre-existing seeded card still renders
260-
// alongside the new "System landscape" card.
261-
await expect(noteCard(page, 'Getting started')).toBeVisible()
262-
await expect(noteCard(page, 'Scratchpad')).toBeVisible()
263-
await expect(groupCard(page, 'Example area')).toBeVisible()
264-
const landscape = groupCard(page, 'System landscape')
265-
await expect(landscape).toBeVisible()
266-
267-
// The seeded perspectives are scoped to "System landscape" -- drill
268-
// in, then switch to "Interim": all three landscape cards render.
269-
await landscape.getByTestId('atlas-group-header').click()
270-
await expect(page.getByTestId('atlas-breadcrumb')).toContainText('System landscape')
255+
// No perspectives ship: the switcher opens with only "All cards"
256+
// and the create affordance.
257+
await switcherButton(page).click()
258+
await expect(switcherPopover(page).getByText('All cards', { exact: true })).toBeVisible()
259+
await expect(switcherPopover(page).getByText('Compare perspectives', { exact: false })).toHaveCount(0)
260+
await page.keyboard.press('Escape')
271261

262+
await createPerspective(page, 'Before')
263+
await createPerspective(page, 'After')
264+
265+
// "After" is active (creation activates): authoring joins it.
266+
const oldTitle = 'ZzE2eCompareOld'
267+
const newTitle = 'ZzE2eCompareNew'
268+
for (const title of [oldTitle, newTitle]) {
269+
await page.getByTestId('atlas-add-button').click()
270+
await page.getByTestId('atlas-add-child').click()
271+
await selectKind(page, ATLAS_KIND_TOPIC, 'atlas-create-kind')
272+
await page.getByTestId('atlas-create-title').fill(title)
273+
await page.getByRole('button', { name: 'Create' }).click()
274+
await expect(noteCard(page, title)).toBeVisible()
275+
}
276+
277+
// The old card also joins "Before" via the context menu.
272278
await switcherButton(page).click()
273-
await switcherPopover(page).getByText('Interim', { exact: true }).click()
274-
await expect(switcherButton(page)).toHaveText('Interim')
275-
await expect(noteCard(page, 'Web app')).toBeVisible()
276-
await expect(noteCard(page, 'Data store')).toBeVisible()
277-
await expect(noteCard(page, 'Sync service')).toBeVisible()
278-
279-
// Compare Current -> Target: Sync service is the only added card,
280-
// the old direct link is the only removed link, and the new
281-
// shape's two links are added.
279+
await switcherPopover(page).getByText('All cards', { exact: true }).click()
280+
await noteCard(page, oldTitle).click({ button: 'right' })
281+
await expect(contextMenu(page)).toBeVisible()
282+
await contextMenu(page).getByText('Add to perspective', { exact: false }).click()
283+
await expect(contextMenu(page)).toBeVisible()
284+
await contextMenu(page).getByText('Before', { exact: true }).click()
285+
await expect(page.getByTestId('atlas-quiet-toast')).toContainText('Added to Before')
286+
287+
// Compare Before -> After: the new card is the one addition; no
288+
// removed groups render (empty groups are omitted).
282289
await switcherButton(page).click()
283290
await switcherPopover(page).getByText('Compare perspectives', { exact: false }).click()
284291
const dialog = page.locator('[data-component="atlas-perspective-compare-dialog"]')
285292
await expect(dialog).toBeVisible()
286-
await dialog.getByTestId('atlas-compare-from').selectOption({ label: 'Current' })
287-
await dialog.getByTestId('atlas-compare-to').selectOption({ label: 'Target' })
293+
await dialog.getByTestId('atlas-compare-from').selectOption({ label: 'Before' })
294+
await dialog.getByTestId('atlas-compare-to').selectOption({ label: 'After' })
288295

289296
const results = dialog.getByTestId('atlas-compare-results')
290297
await expect(results.getByText('Added cards (1)', { exact: true })).toBeVisible()
291-
await expect(results.getByTestId('atlas-compare-card-row').filter({ hasText: 'Sync service' })).toBeVisible()
292-
await expect(results.getByText('Added links (2)', { exact: true })).toBeVisible()
293-
await expect(results.getByText('Removed links (1)', { exact: true })).toBeVisible()
294-
await expect(
295-
results.getByTestId('atlas-compare-link-row').filter({ hasText: 'Web app → Data store (relates to)' }),
296-
).toBeVisible()
297-
// "Removed cards" stays empty (every Current card is also a Target
298-
// member) -- an empty group is omitted entirely, never rendered.
298+
await expect(results.getByTestId('atlas-compare-card-row').filter({ hasText: newTitle })).toBeVisible()
299299
await expect(dialog.getByText('Removed cards', { exact: false })).toHaveCount(0)
300-
301300
await page.keyboard.press('Escape')
302301
await expect(dialog).not.toBeVisible()
302+
303+
// Cleanup (within-file discipline): cards, then perspectives.
304+
for (const title of [oldTitle, newTitle]) {
305+
await openCard(page, noteCard(page, title))
306+
await deleteViaPageMenu(page, page.locator('[data-component="atlas-card-overlay"]'))
307+
}
308+
for (const name of ['Before', 'After']) {
309+
await switcherButton(page).click()
310+
const row = switcherPopover(page).getByText(name, { exact: true })
311+
await row.click({ button: 'right' })
312+
await contextMenu(page).getByText('Delete', { exact: true }).click()
313+
await expect(page.getByRole('heading', { name: `Delete ${name}?` })).toBeVisible()
314+
await page.getByRole('button', { name: 'Delete', exact: true }).click()
315+
await expect(page.getByRole('heading', { name: `Delete ${name}?` })).toHaveCount(0)
316+
if (await switcherPopover(page).isVisible()) await page.keyboard.press('Escape')
317+
}
303318
})
304319
})

frontend/e2e/atlas-projections.spec.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -99,20 +99,17 @@ test('coverage counts a space\'s cards missing a link and missing a mirror, with
9999
await page.getByRole('link', { name: 'Atlas' }).click()
100100
await expect(page.getByTestId('atlas-board')).toBeVisible()
101101

102-
// "My space" has four seeded children: "Getting started" (an
102+
// "My space" has three seeded children: "Getting started" (an
103103
// outgoing "relates to" link to "Ada Lovelace"), "Example area",
104-
// "Scratchpad", and "System landscape" (goal 0095 slice 3's seeded
105-
// perspectives example -- none of the three carries a link of its
106-
// own AT THIS level) -- a hand-countable 1/4 linked. None of the
107-
// four carries a mirror directly at THIS level (the seeded mirror
108-
// lives one level deeper, on "Project charter") -- a hand-countable
109-
// 0/4 mirrored.
104+
// and "Scratchpad" -- a hand-countable 1/3 linked. None carries a
105+
// mirror directly at THIS level (the seeded mirror lives one level
106+
// deeper, on "Project charter") -- a hand-countable 0/3 mirrored.
110107
await page.getByTestId('atlas-open-coverage').click()
111108
const dialog = page.locator('[data-component="atlas-coverage-dialog"]')
112109
await expect(dialog).toBeVisible()
113110

114-
await expect(dialog.getByTestId('atlas-coverage-link-value')).toHaveText('1/4 linked')
115-
await expect(dialog.getByTestId('atlas-coverage-mirror-value')).toHaveText('0/4 mirrored')
111+
await expect(dialog.getByTestId('atlas-coverage-link-value')).toHaveText('1/3 linked')
112+
await expect(dialog.getByTestId('atlas-coverage-mirror-value')).toHaveText('0/3 mirrored')
116113

117114
await dialog.getByTestId('atlas-coverage-link-toggle').click()
118115
await expect(dialog.getByTestId('atlas-coverage-missing-item').filter({ hasText: 'Example area' })).toBeVisible()

frontend/e2e/atlas-select-group.spec.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -470,15 +470,14 @@ test('atlas select-all (Cmd+A): guarded inside an editable field, selects every
470470
await page.keyboard.press('Escape')
471471

472472
// Real dispatch: Cmd+A on the board selects EVERY top-level card at
473-
// this level -- the seeded root ("My space") already carries 4
474-
// (Example area, Getting started, Scratchpad, System landscape --
475-
// goal 0095 slice 3; internal/domain/atlas/builtin.go), plus the 2
476-
// just placed.
473+
// this level -- the seeded root ("My space") carries 3
474+
// (Example area, Getting started, Scratchpad), plus the 2 just
475+
// placed.
477476
await page.keyboard.press('Meta+a')
478-
await expect(selected).toHaveCount(6)
477+
await expect(selected).toHaveCount(5)
479478
const selectionTray = page.getByTestId('atlas-selection-tray')
480479
await expect(selectionTray).toBeVisible()
481-
await expect(page.getByTestId('atlas-selection-count')).toHaveText('6 selected')
480+
await expect(page.getByTestId('atlas-selection-count')).toHaveText('5 selected')
482481

483482
// Cleanup (testing.md's within-file discipline): quick delete +
484483
// clock-controlled toast expiry, same pattern this file's other

internal/domain/atlas/builtin.go

Lines changed: 31 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -238,46 +238,6 @@ func BuiltInCards() []Card {
238238
CreatedAt: now, UpdatedAt: now,
239239
BuiltIn: true, Seed: seedorigin.Stamp(5), // Card gained MirrorChecksum (goal 0088) then DeletedAt (goal 0093) -- shape shifts
240240
},
241-
{
242-
// The seeded perspectives example's own container (goal 0095
243-
// slice 3): a direct child of "My space" (keeps the single-
244-
// root-card navigation intact) holding the three landscape
245-
// cards below, so their own internal links stay invisible one
246-
// level up (resolveBoardEdges.ts skips an edge whose endpoints
247-
// resolve to the same top-level card) -- the pre-existing
248-
// "My space" child census (atlas-projections.spec.ts's
249-
// coverage stat, atlas-scale.spec.ts's dense-fixture edge
250-
// count) only grows by this one new card, never by three.
251-
ID: cardSystemLandscapeID, KindID: kindComponentID, Title: "System landscape",
252-
Note: "Switch perspectives above to see this move from Current to Target.",
253-
ParentID: cardMySpaceID, ViewMode: ViewModeShelves,
254-
// 960 sits right after the seeded row's rightmost card
255-
// (Scratchpad at 746) -- close enough that this card barely
256-
// widens the fit-to-view bounding box other e2e specs'
257-
// zoom-then-click-by-fraction helpers depend on; a much
258-
// farther placement measurably shifts those fractions.
259-
Position: &Position{X: 960, Y: 80},
260-
CreatedAt: now, UpdatedAt: now,
261-
BuiltIn: true, Seed: seedorigin.Stamp(1),
262-
},
263-
{
264-
ID: cardWebAppID, KindID: kindComponentID, Title: "Web app",
265-
ParentID: cardSystemLandscapeID,
266-
CreatedAt: now, UpdatedAt: now,
267-
BuiltIn: true, Seed: seedorigin.Stamp(1),
268-
},
269-
{
270-
ID: cardDataStoreID, KindID: kindComponentID, Title: "Data store",
271-
ParentID: cardSystemLandscapeID,
272-
CreatedAt: now, UpdatedAt: now,
273-
BuiltIn: true, Seed: seedorigin.Stamp(1),
274-
},
275-
{
276-
ID: cardSyncServiceID, KindID: kindComponentID, Title: "Sync service",
277-
ParentID: cardSystemLandscapeID,
278-
CreatedAt: now, UpdatedAt: now,
279-
BuiltIn: true, Seed: seedorigin.Stamp(1),
280-
},
281241
}
282242
}
283243

@@ -298,74 +258,20 @@ func BuiltInLinks() []Link {
298258
CreatedAt: now, UpdatedAt: now,
299259
BuiltIn: true, Seed: seedorigin.Stamp(1),
300260
},
301-
{
302-
// Current's own link: the web app talks to the data store
303-
// directly.
304-
ID: linkWebToStoreID, FromCardID: cardWebAppID, ToCardID: cardDataStoreID,
305-
LinkKindID: linkKindRelatesToID,
306-
CreatedAt: now, UpdatedAt: now,
307-
BuiltIn: true, Seed: seedorigin.Stamp(1),
308-
},
309-
{
310-
// Interim/Target's new shape, half one: the web app talks to
311-
// the sync service.
312-
ID: linkWebToSyncID, FromCardID: cardWebAppID, ToCardID: cardSyncServiceID,
313-
LinkKindID: linkKindRelatesToID,
314-
CreatedAt: now, UpdatedAt: now,
315-
BuiltIn: true, Seed: seedorigin.Stamp(1),
316-
},
317-
{
318-
// Interim/Target's new shape, half two: the sync service
319-
// relays to the data store.
320-
ID: linkSyncToStoreID, FromCardID: cardSyncServiceID, ToCardID: cardDataStoreID,
321-
LinkKindID: linkKindRelatesToID,
322-
CreatedAt: now, UpdatedAt: now,
323-
BuiltIn: true, Seed: seedorigin.Stamp(1),
324-
},
325261
}
326262
}
327263

328-
// BuiltInPerspectives returns the seeded three-perspective reference-
329-
// architecture example (ADR-0041, goal 0095 slice 3): "Current",
330-
// "Interim", "Target" over "System landscape"'s own three Component
331-
// cards, membership telling a migration story -- Current is the web
332-
// app wired straight to the data store; Interim adds the sync service
333-
// alongside that old connection; Target drops the old direct link and
334-
// keeps only the new shape. Every card is a member of every
335-
// perspective (the story is entirely which LINKS are visible); only
336-
// MemberLinkIDs differs.
264+
// BuiltInPerspectives seeds NO perspectives. Perspectives are a
265+
// platform capability; a named set like "Current/Interim/Target" is a
266+
// modeling concept the USER authors -- shipping one made it read as a
267+
// native option in the switcher on every install (the inner-platform
268+
// smell SPEC section 0 exists to prevent). The capability's proof
269+
// lives at the test layer (dedicated-server e2e + Go integration
270+
// tests build their own perspectives) and in the docs, never as
271+
// always-present named objects in user data. The retired IDs below
272+
// remove the previously-shipped set from existing installs.
337273
func BuiltInPerspectives() []Perspective {
338-
now := time.Now()
339-
allThree := []string{cardWebAppID, cardDataStoreID, cardSyncServiceID}
340-
return []Perspective{
341-
{
342-
ID: perspectiveCurrentID, SpaceID: cardSystemLandscapeID, Name: "Current",
343-
Description: "The web app talks straight to the data store.",
344-
Order: 0,
345-
MemberCardIDs: []string{cardWebAppID, cardDataStoreID},
346-
MemberLinkIDs: []string{linkWebToStoreID},
347-
CreatedAt: now, UpdatedAt: now,
348-
BuiltIn: true, Seed: seedorigin.Stamp(1),
349-
},
350-
{
351-
ID: perspectiveInterimID, SpaceID: cardSystemLandscapeID, Name: "Interim",
352-
Description: "The sync service comes online alongside the old connection.",
353-
Order: 1,
354-
MemberCardIDs: allThree,
355-
MemberLinkIDs: []string{linkWebToStoreID, linkWebToSyncID, linkSyncToStoreID},
356-
CreatedAt: now, UpdatedAt: now,
357-
BuiltIn: true, Seed: seedorigin.Stamp(1),
358-
},
359-
{
360-
ID: perspectiveTargetID, SpaceID: cardSystemLandscapeID, Name: "Target",
361-
Description: "The old direct connection is gone; the sync service is the only path.",
362-
Order: 2,
363-
MemberCardIDs: allThree,
364-
MemberLinkIDs: []string{linkWebToSyncID, linkSyncToStoreID},
365-
CreatedAt: now, UpdatedAt: now,
366-
BuiltIn: true, Seed: seedorigin.Stamp(1),
367-
},
368-
}
274+
return nil
369275
}
370276

371277
// RetiredBuiltInKindIDs names a built-in Kind ID that once shipped in
@@ -389,3 +295,24 @@ func RetiredKindReplacementID(retiredID string) (string, bool) {
389295
}
390296
return "", false
391297
}
298+
299+
// RetiredBuiltInCardIDs names built-in cards that once shipped and no
300+
// longer do -- the reference-architecture landscape (its concept now
301+
// user-authored, never seeded). Reconcile removes an install's copy
302+
// exactly when it is still an untouched golden (Seed.Modified false);
303+
// an edited copy belongs to the user and stays.
304+
func RetiredBuiltInCardIDs() []string {
305+
return []string{cardWebAppID, cardDataStoreID, cardSyncServiceID, cardSystemLandscapeID}
306+
}
307+
308+
// RetiredBuiltInLinkIDs names the retired landscape's own links, same
309+
// contract as RetiredBuiltInCardIDs.
310+
func RetiredBuiltInLinkIDs() []string {
311+
return []string{linkWebToStoreID, linkWebToSyncID, linkSyncToStoreID}
312+
}
313+
314+
// RetiredBuiltInPerspectiveIDs names the previously-seeded perspective
315+
// set, same contract as RetiredBuiltInCardIDs.
316+
func RetiredBuiltInPerspectiveIDs() []string {
317+
return []string{perspectiveCurrentID, perspectiveInterimID, perspectiveTargetID}
318+
}

0 commit comments

Comments
 (0)