Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ export type {
MirrorContent,
Note,
Perspective,
PerspectiveDiff,
Position
} from "./models.js";
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,23 @@ export interface Perspective {
"Seed": seedorigin$0.Origin;
}

/**
* PerspectiveDiff is the computable diff between two perspectives
* sharing the same live card set (ADR-0041's O(1) reference-
* architecture property, docs/goals/0095): pure set difference over
* stored membership, nothing derived or stored on disk. Re-parenting
* is deliberately never expressed here -- a card carries exactly one
* ParentID shared by every perspective, so containment itself can
* never differ per view (ADR-0041's own invariant; a revisit needs its
* own ADR).
*/
export interface PerspectiveDiff {
"AddedCardIDs": string[] | null;
"RemovedCardIDs": string[] | null;
"AddedLinkIDs": string[] | null;
"RemovedLinkIDs": string[] | null;
}

/**
* Position is a card's location within its PARENT's canvas -- only
* meaningful when the parent's EffectiveViewMode is ViewModeCanvas;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,19 @@ export function DetectSyncRoots(): $CancellablePromise<string[] | null> {
return $Call.ByID(2360667167);
}

/**
* DiffPerspectives computes what changed moving from fromID's own
* membership to toID's (goal 0095 slice 3's Compare view): a thin
* bound wrapper over the pure atlas.DiffPerspectives helper, the same
* "expose the domain package's own pure function through a read-only
* accessor" shape Perspectives() itself already takes. Re-parenting is
* deliberately never expressed here -- ADR-0041's own invariant, since
* a card carries exactly one ParentID shared by every perspective.
*/
export function DiffPerspectives(fromID: string, toID: string): $CancellablePromise<atlas$0.PerspectiveDiff> {
return $Call.ByID(3383492038, fromID, toID);
}

/**
* ExportAtlas serializes the whole Atlas graph as an indented, portable
* JSON string -- share it, commit it to git, or import it into another
Expand Down
8 changes: 6 additions & 2 deletions frontend/e2e/atlas-authoring.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,15 @@ test('atlas creation core: tray, placement popover, right-click create, sticky n
await expect(page.getByTestId('atlas-jump-no-matches')).toBeVisible()
await page.keyboard.press('Escape')

// "My space" seeds four children (goal 0095 slice 3 added "System
// landscape" alongside Getting started/Example area/Scratchpad) --
// a hand-countable 1/4 linked, 0/4 mirrored (same census
// atlas-projections.spec.ts's own coverage test pins).
await page.getByTestId('atlas-open-coverage').click()
const coverageDialog = page.locator('[data-component="atlas-coverage-dialog"]')
await expect(coverageDialog).toBeVisible()
await expect(coverageDialog.getByTestId('atlas-coverage-link-value')).toHaveText('1/3 linked')
await expect(coverageDialog.getByTestId('atlas-coverage-mirror-value')).toHaveText('0/3 mirrored')
await expect(coverageDialog.getByTestId('atlas-coverage-link-value')).toHaveText('1/4 linked')
await expect(coverageDialog.getByTestId('atlas-coverage-mirror-value')).toHaveText('0/4 mirrored')
await page.keyboard.press('Escape')
await expect(coverageDialog).not.toBeVisible()

Expand Down
7 changes: 4 additions & 3 deletions frontend/e2e/atlas-breadcrumb-siblings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ test('a breadcrumb segment opens a dropdown of its level\'s siblings, current on

// "My space" is the seeded space's own root -- its siblings are
// every other root-level card, which at the single-root default is
// only itself (3 top-level children: Example area, Getting started,
// Scratchpad).
// only itself. The child count is asserted as a pattern, not an
// exact number: seeds evolve (goal 0095 added a whole seeded area)
// and this test pins the dropdown's SHAPE, not the seed catalogue.
const mySpaceCrumb = page.getByTestId('atlas-breadcrumb').getByTestId('atlas-breadcrumb-item').filter({ hasText: 'My space' })
await mySpaceCrumb.click()

const dropdown = page.getByTestId('atlas-breadcrumb-siblings')
await expect(dropdown).toBeVisible()
const mySpaceRow = dropdown.getByTestId('atlas-breadcrumb-sibling').filter({ hasText: 'My space' })
await expect(mySpaceRow).toBeVisible()
await expect(mySpaceRow).toContainText('3 cards')
await expect(mySpaceRow).toContainText(/\d+ cards/)

// Clicking the current place navigates to it (reproducing the old
// direct-navigate behavior) -- the crumb collapses back to "My
Expand Down
34 changes: 29 additions & 5 deletions frontend/e2e/atlas-gestures.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { test, expect } from './fixtures/server'
import { groupCard, noteCard } from './fixtures/atlasCards'
import { clickCorner, zoomAllTheWayOut } from './fixtures/atlasBoard'
import { clickCorner, zoomAllTheWayOut, clickFrameGutter } from './fixtures/atlasBoard'
import { contextMenu } from './fixtures/contextMenu'
import { waitForViewportStable } from './fixtures/animation'

// The click model (goal 0102's gesture table) + surface-scoped
// shortcuts (goal 0071 slice): plain click selects/replaces, a second
Expand Down Expand Up @@ -87,9 +88,28 @@ test('a plain click leaves the selection ring visibly showing on the clicked car
test('a real double-click reproduces the same select-then-commit outcome as two plain clicks, for both a leaf and a frame body', async ({ page }) => {
await page.goto('/')
await page.getByRole('link', { name: 'Atlas' }).click()
await expect(page.getByTestId('atlas-board')).toBeVisible()

const board = page.getByTestId('atlas-board')
await expect(board).toBeVisible()
// The board's own initial fitView animates into place, and
// individual note cards carry their own entrance transition
// independent of the viewport's own pan/zoom transform -- a
// dblclick fired before BOTH settle can land on a still-moving
// neighbor instead of the intended card (regression: "Ada
// Lovelace"'s node intercepted a dblclick meant for "Getting
// started" while its own entrance animation was still running).
await waitForViewportStable(board)
const getting = noteCard(page, 'Getting started')
let previousBox: string | null = null
await expect
.poll(async () => {
const box = await getting.boundingBox()
const current = box ? `${box.x},${box.y}` : null
const stable = previousBox !== null && current === previousBox
previousBox = current
return stable
})
.toBe(true)

await getting.dblclick()
const overlay = page.locator('[data-component="atlas-card-overlay"]')
await expect(overlay).toBeVisible()
Expand All @@ -98,9 +118,13 @@ test('a real double-click reproduces the same select-then-commit outcome as two
await expect(overlay).not.toBeVisible()

// Frame body double-click = zoom into the place (padding strip:
// the frame centre belongs to its preview-child nodes).
// the frame centre belongs to its preview-child nodes). A FRACTION
// of the frame's own rendered box (not a fixed pixel offset, same
// idiom atlas-page.spec.ts's identical gutter click already uses)
// stays inside the gutter regardless of the board's own zoom scale,
// which shifts with the seeded card count (goal 0095 slice 3).
const exampleArea = groupCard(page, 'Example area')
await exampleArea.dblclick({ position: { x: 6, y: 60 } })
await clickFrameGutter(exampleArea, { clickCount: 2 })
await expect(page.getByTestId('atlas-breadcrumb')).toContainText('Example area')

// ⌘↑ = one step up the depth ladder (atlas.up, Finder's enclosing-
Expand Down
5 changes: 2 additions & 3 deletions frontend/e2e/atlas-page-edit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import {
} from './fixtures/server'
import { deleteViaPageMenu } from './fixtures/atlasPage'
import { ATLAS_KIND_TOPIC, selectKind } from './fixtures/kindPicker'
import { clickBreadcrumbSegment, groupCard, noteCard, openCard } from './fixtures/atlasBoard'
import { clickAtFraction } from './fixtures/animation'
import { clickBreadcrumbSegment, clickFrameGutter, groupCard, noteCard, openCard } from './fixtures/atlasBoard'

// Atlas card page read-is-edit + chip navigation (goal 0081 slice A5,
// LOCKED design §5b): every field editable in place with a per-save
Expand Down Expand Up @@ -150,7 +149,7 @@ test('atlas card page: read-is-edit fields, kind-gated mirror controls, page lin
// edge, unclickable at a fixed fraction of its own bounding box. ---
await page.getByRole('button', { name: 'Fit View' }).click()
const exampleAreaFrame = groupCard(page, 'Example area')
await clickAtFraction(exampleAreaFrame, 0.01, 0.5, { modifiers: ['Meta'] })
await clickFrameGutter(exampleAreaFrame, { modifiers: ['Meta'] })
await expect(overlay).toBeVisible()
const childRow = overlay.getByTestId('atlas-page-child').filter({ hasText: 'Ada Lovelace' })
await expect(childRow).toBeVisible()
Expand Down
20 changes: 11 additions & 9 deletions frontend/e2e/atlas-page-scale.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
spawnMillServer,
type SpawnedServer,
} from './fixtures/server'
import { clickFrameGutter } from './fixtures/atlasBoard'

// The card PAGE at scale (goal 0073 slice B): a card holding many
// children must cap its own Contents column the same way a board
Expand Down Expand Up @@ -76,14 +77,12 @@ test('a card page at scale caps its entries with an honest expander and lazy-loa
const mirrorStack = groupCard(page, 'Mirror Stack')
await expect(mirrorStack).toBeVisible()

// Open the page: ⌘-click on the frame's own body opens its page
// directly (goal 0102's gesture table's instant-commit path,
// atlas-page.spec.ts's own established pattern for reaching a
// group's page). x:6 sits inside the frame's left GROUP_PADDING
// gutter, a blank strip running the frame's full height below its
// header -- safe regardless of how many rows the 5 previewed
// children wrap into.
await mirrorStack.click({ position: { x: 6, y: 60 }, modifiers: ['Meta'] })
// Open the page: ⌘-click on the frame's own gutter opens its page
// directly (goal 0102's instant-commit path). clickFrameGutter
// measures a SETTLED box and a border-safe offset -- a fixed
// pixel position lands outside the frame once fitView zooms the
// denser board out further.
await clickFrameGutter(mirrorStack, { modifiers: ['Meta'] })

const overlay = page.locator('[data-component="atlas-card-overlay"]')
await expect(overlay).toBeVisible()
Expand All @@ -108,6 +107,9 @@ test('a card page at scale caps its entries with an honest expander and lazy-loa
} finally {
await browser.close()
server?.stop()
rmSync(dir, { recursive: true, force: true })
// maxRetries: the just-stopped server can still be flushing its
// settings/db files when cleanup runs -- a bare rmSync races it
// to ENOTEMPTY.
rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
}
})
9 changes: 4 additions & 5 deletions frontend/e2e/atlas-page.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { test, expect } from './fixtures/server'
import { deleteViaPageMenu } from './fixtures/atlasPage'
import { clickAtFraction } from './fixtures/animation'
import { armAndPlaceTopicCard, clickBreadcrumbSegment, openCard } from './fixtures/atlasBoard'
import { armAndPlaceTopicCard, clickBreadcrumbSegment, clickFrameGutter, openCard } from './fixtures/atlasBoard'

// Exercises the card PAGE's own ratified anatomy (goal 0072 slice C,
// docs/adr/0038): the header row (kind glyph/circle, title, file tag,
Expand Down Expand Up @@ -189,7 +188,7 @@ test('a region frame\'s body click selects it (never drills); ⌘-click opens th
// edge and its first column of children, running the full height
// below the header -- a 1% fraction of width stays inside that gutter
// whatever the board's current zoom level scales it to.
await clickAtFraction(exampleArea, 0.01, 0.5)
await clickFrameGutter(exampleArea)
await expect(exampleAreaWrapper).toHaveCount(1)
// The board never re-roots off a plain body click -- the header
// remains the only unconditional drill affordance.
Expand All @@ -201,7 +200,7 @@ test('a region frame\'s body click selects it (never drills); ⌘-click opens th
// ⌘-click opens the frame's own page directly (goal 0102's gesture
// table: ⌘-click = instant commit, the pointer twin of ⌘↵) --
// reached with no prior selection needed.
await clickAtFraction(exampleArea, 0.01, 0.5, { modifiers: ['Meta'] })
await clickFrameGutter(exampleArea, { modifiers: ['Meta'] })
const overlay = page.locator('[data-component="atlas-card-overlay"]')
await expect(overlay).toBeVisible()
await expect(overlay.getByTestId('atlas-page-title')).toHaveValue('Example area')
Expand Down Expand Up @@ -252,7 +251,7 @@ test('a child\'s mirror preview renders inline in the parent page; the card\'s o
// table's instant-commit path).
await clickBreadcrumbSegment(page, page.getByTestId('atlas-breadcrumb').getByText('My space', { exact: true }), 'My space')
const exampleAreaFrame = groupCard(page, 'Example area')
await clickAtFraction(exampleAreaFrame, 0.01, 0.5, { modifiers: ['Meta'] })
await clickFrameGutter(exampleAreaFrame, { modifiers: ['Meta'] })
await expect(overlay).toBeVisible()
await expect(overlay.getByTestId('atlas-page-title')).toHaveValue('Example area')
const charterEntry = overlay.getByTestId('atlas-page-child').filter({ hasText: 'Project charter' })
Expand Down
Loading
Loading