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 @@ -15,5 +15,6 @@ export type {
LinkKind,
MirrorContent,
Note,
Perspective,
Position
} from "./models.js";
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,49 @@ export interface Note {
"DeletedAt": string;
}

/**
* Perspective is a named, ordered view over one container's (SpaceID's)
* live card set (ADR-0041, goal 0095): membership is explicit and
* stored -- both which cards (MemberCardIDs) and which links
* (MemberLinkIDs) render in this view -- never derived from a query,
* so a target-state connection between two currently-existing systems
* can never leak into a view it wasn't deliberately added to. The
* default "everything" view is the ABSENCE of a Perspective record:
* zero Perspectives over a space means zero behavior change. Order
* places this perspective within its SpaceID's own ordered set (e.g.
* "Current", "Interim", "Target"); it is meaningless compared across
* different SpaceID values. Deliberately carries NO DeletedAt --
* unlike Card/Note (goal 0093's soft-delete-with-undo), a Perspective
* delete is immediate: cheap to recreate, so no tombstone/undo
* lifecycle exists for it in v1.
*/
export interface Perspective {
"ID": string;
"SpaceID": string;
"Name": string;
"Description": string;
"Order": number;

/**
* MemberCardIDs is the stored set of cards this perspective shows.
* Closed under ancestry by the service layer's AddToPerspective (a
* member's containing chain up to SpaceID joins with it), never by
* this package -- membership mutation is a service concern.
*/
"MemberCardIDs": string[] | null;

/**
* MemberLinkIDs is the stored set of links this perspective shows.
* A link still only actually RENDERS when both its endpoints are
* ALSO members -- see FilterByPerspective.
*/
"MemberLinkIDs": string[] | null;
"CreatedAt": string;
"UpdatedAt": string;
"BuiltIn": boolean;
"Seed": seedorigin$0.Origin;
}

/**
* 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 @@ -37,6 +37,17 @@ export function AddLinkedCard(fromCardID: string, kindID: string, title: string,
return $Call.ByID(1483030434, fromCardID, kindID, title, position);
}

/**
* AddToPerspective adds cardID to perspectiveID, closed under ancestry
* (ADR-0041): every container between cardID and the perspective's own
* SpaceID joins with it, so a deeply nested card never appears in a
* filtered view floating with no visible container. Rejects a cardID
* that does not actually live inside the perspective's space.
*/
export function AddToPerspective(perspectiveID: string, cardID: string): $CancellablePromise<atlas$0.Perspective> {
return $Call.ByID(420793018, perspectiveID, cardID);
}

/**
* AtlasSession returns the persisted state, DEGRADED to what still
* exists: a fully-gone viewed card falls back to root; a tombstoned
Expand Down Expand Up @@ -170,6 +181,14 @@ export function CreateNote(text: string, pos: atlas$0.Position, parentID: string
return $Call.ByID(1216295546, text, pos, parentID);
}

/**
* CreatePerspective makes a new Perspective over spaceID ("" for the
* true root), appended at the end of that space's own ordered set.
*/
export function CreatePerspective(spaceID: string, name: string, description: string): $CancellablePromise<atlas$0.Perspective> {
return $Call.ByID(3910340284, spaceID, name, description);
}

/**
* DeleteCard soft-deletes a card (goal 0093's quick-delete-with-undo
* guard): stamps DeletedAt and leaves ParentID/children untouched --
Expand Down Expand Up @@ -215,6 +234,17 @@ export function DeleteNote(id: string): $CancellablePromise<$models.TombstoneRes
return $Call.ByID(3922503309, id);
}

/**
* DeletePerspective removes a perspective immediately -- no tombstone/
* undo lifecycle (ADR-0041): cheap to recreate. A session parked on
* this perspective degrades to the everything view the next time
* AtlasSession is read; nothing else references a Perspective's id, so
* there is no further referential cleanup to do here.
*/
export function DeletePerspective(id: string): $CancellablePromise<void> {
return $Call.ByID(2541135077, id);
}

/**
* DetectSyncRoots reports which well-known cloud-sync folders
* (OneDrive/Dropbox/iCloud Drive) exist on disk, most-likely-first --
Expand Down Expand Up @@ -360,6 +390,16 @@ export function OpenCardMirror(cardID: string): $CancellablePromise<void> {
return $Call.ByID(3356986203, cardID);
}

/**
* Perspectives returns every perspective across every space -- the
* frontend/caller scopes by SpaceID (goal 0095's slice-1 read model
* mirrors Kinds()/LinkKinds()'s own "return everything, caller
* filters" shape).
*/
export function Perspectives(): $CancellablePromise<atlas$0.Perspective[] | null> {
return $Call.ByID(1253153793);
}

/**
* PickFolder opens the native folder picker -- goal 0067's consent
* gate: zero filesystem reads happen before this returns a path the
Expand All @@ -386,6 +426,36 @@ export function PromoteNote(noteID: string, kindID: string, title: string): $Can
return $Call.ByID(881716522, noteID, kindID, title);
}

/**
* RemoveFromPerspective removes cardID from perspectiveID, cascaded to
* every member descendant cardID currently contains (ADR-0041): a
* member left behind after its own container drops out of a view would
* render with no visible parent context.
*/
export function RemoveFromPerspective(perspectiveID: string, cardID: string): $CancellablePromise<atlas$0.Perspective> {
return $Call.ByID(2634584680, perspectiveID, cardID);
}

/**
* RenamePerspective replaces a perspective's Name/Description in
* place -- membership/SpaceID/Order move through their own dedicated
* calls (AddToPerspective/RemoveFromPerspective/ReorderPerspective),
* same "one concern per mutator" shape UpdateCard vs. MoveCard takes.
*/
export function RenamePerspective(id: string, name: string, description: string): $CancellablePromise<atlas$0.Perspective> {
return $Call.ByID(994641070, id, name, description);
}

/**
* ReorderPerspective assigns Order = index within orderedIDs to every
* perspective named -- orderedIDs must name EXACTLY the perspectives
* currently scoped to spaceID, once each (the same "whole ordered set,
* not a partial move" shape a drag-reorder list naturally produces).
*/
export function ReorderPerspective(spaceID: string, orderedIDs: string[] | null): $CancellablePromise<void> {
return $Call.ByID(538428937, spaceID, orderedIDs);
}

/**
* ResolveFileDropRoute decides what a drop/paste of paths means --
* stats them (never lists a directory's contents; that's ScanFolder's
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export interface AtlasImportSummary {
"CardsUpdated": number;
"LinksCreated": number;
"LinksUpdated": number;
"PerspectivesCreated": number;
"PerspectivesUpdated": number;
}

/**
Expand All @@ -34,6 +36,15 @@ export interface AtlasImportSummary {
export interface AtlasSessionState {
"viewedID": string;
"openCardID": string;

/**
* ActivePerspectiveID names the currently-switched-to Perspective
* (ADR-0041, goal 0095) -- "" is the default "everything" view (the
* absence of a Perspective, never an empty one). Degraded on read
* like its siblings: a perspective deleted since this was set
* resolves back to "".
*/
"activePerspectiveID": string;
}

/**
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/atlas/AtlasView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ export function AtlasView({ initialCardID }: { initialCardID?: string }) {
}, [])
useEffect(() => {
if (!sessionRestored) return
void AtlasService.SetAtlasSession({ viewedID, openCardID: overlayCardID ?? '' }).catch(() => {})
// activePerspectiveID: the switcher UI lands in a later slice (goal
// 0095) -- always '' (the everything view) here for now.
void AtlasService.SetAtlasSession({ viewedID, openCardID: overlayCardID ?? '', activePerspectiveID: '' }).catch(() => {})
}, [sessionRestored, viewedID, overlayCardID])

useEffect(() => {
Expand Down
43 changes: 43 additions & 0 deletions internal/contract/contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,43 @@
"label"
]
},
"exportedPerspective": {
"properties": {
"id": {
"type": "string"
},
"spaceID": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"order": {
"type": "integer"
},
"memberCardIDs": {
"items": {
"type": "string"
},
"type": "array"
},
"memberLinkIDs": {
"items": {
"type": "string"
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"name",
"order"
]
},
"exportedPosition": {
"properties": {
"x": {
Expand Down Expand Up @@ -279,6 +316,12 @@
"$ref": "#/$defs/exportedLink"
},
"type": "array"
},
"perspectives": {
"items": {
"$ref": "#/$defs/exportedPerspective"
},
"type": "array"
}
},
"additionalProperties": false,
Expand Down
43 changes: 43 additions & 0 deletions internal/contract/schemas/atlas.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,43 @@
"label"
]
},
"exportedPerspective": {
"properties": {
"id": {
"type": "string"
},
"spaceID": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"order": {
"type": "integer"
},
"memberCardIDs": {
"items": {
"type": "string"
},
"type": "array"
},
"memberLinkIDs": {
"items": {
"type": "string"
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"name",
"order"
]
},
"exportedPosition": {
"properties": {
"x": {
Expand Down Expand Up @@ -244,6 +281,12 @@
"$ref": "#/$defs/exportedLink"
},
"type": "array"
},
"perspectives": {
"items": {
"$ref": "#/$defs/exportedPerspective"
},
"type": "array"
}
},
"additionalProperties": false,
Expand Down
12 changes: 12 additions & 0 deletions internal/domain/atlas/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,18 @@ func BuiltInLinks() []Link {
}
}

// BuiltInPerspectives returns the seeded example perspectives (ADR-
// 0041, goal 0095) -- deliberately EMPTY in this slice (the seeded
// three-perspective reference-architecture example is goal 0095's
// slice 3): the reconcile/fingerprint plumbing exists and runs on
// every startup like every other family's, it just has nothing to
// insert yet. Zero Perspectives is also the product default itself
// (the "everything" view is the absence of a record), so an empty
// slice here changes nothing observable.
func BuiltInPerspectives() []Perspective {
return nil
}

// RetiredBuiltInKindIDs names a built-in Kind ID that once shipped in
// BuiltInKinds() and no longer does -- atlassvc's reconcile pass uses
// this to remove/tombstone a leftover copy from an existing install,
Expand Down
Loading
Loading