@formicoidea/labre-core 0.59.0 → 0.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/architecture/code.c4 +111 -104
  2. package/architecture/domain.c4 +4 -4
  3. package/dist/_pkgs/affine-block-attachment/asset-picker.d.ts +8 -0
  4. package/dist/_pkgs/affine-block-attachment/asset-picker.js +25 -1
  5. package/dist/_pkgs/affine-block-attachment/attachment-block.js +9 -7
  6. package/dist/_pkgs/affine-block-database/database-block.js +4 -5
  7. package/dist/_pkgs/affine-block-database/service/index.d.ts +1 -0
  8. package/dist/_pkgs/affine-block-database/service/index.js +1 -0
  9. package/dist/_pkgs/affine-block-database/service/initial-view.d.ts +33 -0
  10. package/dist/_pkgs/affine-block-database/service/initial-view.js +20 -0
  11. package/dist/_pkgs/affine-block-database/utils/current-view.d.ts +7 -0
  12. package/dist/_pkgs/affine-block-database/utils/current-view.js +14 -0
  13. package/dist/_pkgs/affine-block-embed/common/render-linked-doc.js +2 -0
  14. package/dist/_pkgs/affine-block-embed-doc/common/child-card.d.ts +25 -0
  15. package/dist/_pkgs/affine-block-embed-doc/common/child-card.js +27 -0
  16. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/commands/insert-embed-linked-doc.js +6 -1
  17. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/configs/new-doc.js +2 -2
  18. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/embed-linked-doc-block.js +2 -6
  19. package/dist/_pkgs/affine-block-embed-doc/embed-synced-doc-block/embed-synced-doc-block.js +2 -3
  20. package/dist/_pkgs/affine-block-embed-doc/page-drop/page-drop.js +3 -3
  21. package/dist/_pkgs/affine-block-surface/extensions/index.d.ts +1 -0
  22. package/dist/_pkgs/affine-block-surface/extensions/index.js +1 -0
  23. package/dist/_pkgs/affine-block-surface/extensions/legend-label.d.ts +11 -0
  24. package/dist/_pkgs/affine-block-surface/extensions/legend-label.js +16 -0
  25. package/dist/_pkgs/affine-block-surface/extensions/legend.d.ts +1 -11
  26. package/dist/_pkgs/affine-block-surface/extensions/legend.js +2 -11
  27. package/dist/_pkgs/affine-block-surface/extensions/toolbox.d.ts +62 -0
  28. package/dist/_pkgs/affine-block-surface/extensions/toolbox.js +1 -0
  29. package/dist/_pkgs/affine-block-surface/interchange-headless.d.ts +2 -0
  30. package/dist/_pkgs/affine-block-surface/interchange-headless.js +2 -0
  31. package/dist/_pkgs/affine-components/icons/loading.d.ts +6 -0
  32. package/dist/_pkgs/affine-components/icons/loading.js +16 -12
  33. package/dist/_pkgs/affine-fragment-doc-title/title-band/show-here.js +2 -2
  34. package/dist/_pkgs/affine-fragment-doc-title/title-band/title-band.d.ts +8 -0
  35. package/dist/_pkgs/affine-fragment-doc-title/title-band/title-band.js +35 -13
  36. package/dist/_pkgs/affine-fragment-doc-title/translations.d.ts +1 -1
  37. package/dist/_pkgs/affine-fragment-doc-title/translations.js +1 -1
  38. package/dist/_pkgs/affine-gfx-mindmap/toolbar/basket-elements.d.ts +12 -0
  39. package/dist/_pkgs/affine-gfx-mindmap/toolbar/basket-elements.js +44 -5
  40. package/dist/_pkgs/affine-gfx-mindmap/toolbar/senior-tool.d.ts +3 -2
  41. package/dist/_pkgs/affine-gfx-mindmap/toolbar/senior-tool.js +3 -2
  42. package/dist/_pkgs/affine-gfx-template/builtin/slides/build.js +10 -10
  43. package/dist/_pkgs/affine-gfx-template/builtin/slides/consts.d.ts +48 -64
  44. package/dist/_pkgs/affine-gfx-template/builtin/slides/consts.js +24 -21
  45. package/dist/_pkgs/affine-gfx-template/builtin/slides/masks.d.ts +1 -2
  46. package/dist/_pkgs/affine-gfx-template/builtin/slides/masks.js +0 -34
  47. package/dist/_pkgs/affine-inline-reference/reference-node/configs/toolbar.js +2 -1
  48. package/dist/_pkgs/affine-model/elements/c4/board.d.ts +0 -22
  49. package/dist/_pkgs/affine-model/elements/c4/board.js +1 -22
  50. package/dist/_pkgs/affine-model/elements/c4/consts.d.ts +29 -0
  51. package/dist/_pkgs/affine-model/elements/c4/consts.js +29 -0
  52. package/dist/_pkgs/affine-model/elements/c4/index.d.ts +1 -0
  53. package/dist/_pkgs/affine-model/elements/c4/index.js +1 -0
  54. package/dist/_pkgs/affine-model/headless.d.ts +1 -0
  55. package/dist/_pkgs/affine-model/headless.js +1 -0
  56. package/dist/_pkgs/affine-shared/consts/index.d.ts +9 -0
  57. package/dist/_pkgs/affine-shared/consts/index.js +9 -0
  58. package/dist/_pkgs/affine-shared/services/asset-picker-service.d.ts +7 -6
  59. package/dist/_pkgs/affine-shared/services/asset-picker-service.js +5 -5
  60. package/dist/_pkgs/affine-shared/services/title-band-service.d.ts +19 -0
  61. package/dist/_pkgs/data-view/core/component/cell-status/cell-status-css.d.ts +1 -1
  62. package/dist/_pkgs/data-view/core/component/cell-status/cell-status-css.js +10 -10
  63. package/dist/_pkgs/data-view/core/component/cell-status/cell-status.d.ts +1 -1
  64. package/dist/_pkgs/data-view/core/component/cell-status/cell-status.js +7 -5
  65. package/dist/_pkgs/data-view/property-presets/number/cell-renderer.d.ts +18 -0
  66. package/dist/_pkgs/data-view/property-presets/number/cell-renderer.js +51 -10
  67. package/dist/_pkgs/data-view/property-presets/text/cell-renderer.d.ts +23 -1
  68. package/dist/_pkgs/data-view/property-presets/text/cell-renderer.js +56 -15
  69. package/dist/_pkgs/data-view/property-presets/typing-commit.d.ts +16 -0
  70. package/dist/_pkgs/data-view/property-presets/typing-commit.js +27 -0
  71. package/dist/_pkgs/data-view/view-presets/list/list-view.js +1 -1
  72. package/package.json +1 -1
@@ -708,7 +708,7 @@ model {
708
708
  extend features.blocks.surface {
709
709
  metadata {
710
710
  domain ['canvas', 'file', 'layer']
711
- domain_canvas '0.74'
711
+ domain_canvas '0.75'
712
712
  domain_file '0.17'
713
713
  domain_layer '0.05'
714
714
  }
@@ -770,8 +770,8 @@ model {
770
770
  extend features.blocks.surface.extensions {
771
771
  metadata {
772
772
  domain ['canvas', 'file', 'layer']
773
- domain_canvas '0.52'
774
- domain_file '0.30'
773
+ domain_canvas '0.53'
774
+ domain_file '0.29'
775
775
  domain_layer '0.12'
776
776
  }
777
777
  }
@@ -2422,7 +2422,7 @@ model {
2422
2422
  metadata {
2423
2423
  domain ['document', 'canvas']
2424
2424
  domain_document '0.72'
2425
- domain_canvas '0.23'
2425
+ domain_canvas '0.24'
2426
2426
  }
2427
2427
  }
2428
2428
  extend model_.model_.blocks {
@@ -2,6 +2,7 @@ import { type AssetRef } from '../affine-shared/services/index.js';
2
2
  import type { IVec } from '../global/gfx/index.js';
3
3
  import type { BlockStdScope } from '../std/index.js';
4
4
  import type { BlockModel } from '../store/index.js';
5
+ import { type AttachmentBlockSource } from './utils.js';
5
6
  /**
6
7
  * Adds one attachment card per ref next to `targetModel`, as one undo step.
7
8
  * Nothing is written on a read-only document.
@@ -22,3 +23,10 @@ export declare function addAttachmentsFromAssets(std: BlockStdScope, assets: rea
22
23
  export declare function pickAndAddSiblingAttachmentBlocks(std: BlockStdScope, targetModel: BlockModel): Promise<string[]>;
23
24
  /** The canvas's file button: the host's asset dialog, or the file chooser. */
24
25
  export declare function pickAndAddAttachments(std: BlockStdScope): Promise<string[]>;
26
+ /**
27
+ * What the attachment toolbar's "Replace" puts in the card: the host's asset
28
+ * dialog when there is one (`kind: 'file'`, `mode: 'replace'`, first ref
29
+ * used, by reference), the system file chooser otherwise (the file goes up
30
+ * through the blob source, as before). `null` when cancelled.
31
+ */
32
+ export declare function pickAttachmentSource(std: BlockStdScope): Promise<AttachmentBlockSource | null>;
@@ -1,6 +1,6 @@
1
1
  import { AssetPickerProvider, } from '../affine-shared/services/index.js';
2
2
  import { openSingleFileWith } from '../affine-shared/utils/index.js';
3
- import { addAttachments, addSiblingAttachmentBlocks, placeAttachmentsOnCanvas, } from './utils.js';
3
+ import { addAttachments, addSiblingAttachmentBlocks, getFileType, placeAttachmentsOnCanvas, } from './utils.js';
4
4
  /**
5
5
  * Attachments from the host's asset library (`AssetPickerProvider`, labre
6
6
  * P4): the card references a blob the host already stores, by its
@@ -85,3 +85,27 @@ export async function pickAndAddAttachments(std) {
85
85
  return [];
86
86
  return addAttachments(std, [file]);
87
87
  }
88
+ /**
89
+ * What the attachment toolbar's "Replace" puts in the card: the host's asset
90
+ * dialog when there is one (`kind: 'file'`, `mode: 'replace'`, first ref
91
+ * used, by reference), the system file chooser otherwise (the file goes up
92
+ * through the blob source, as before). `null` when cancelled.
93
+ */
94
+ export async function pickAttachmentSource(std) {
95
+ const picker = std.getOptional(AssetPickerProvider);
96
+ if (picker) {
97
+ const [asset] = (await picker.pick({
98
+ kind: 'file',
99
+ mode: 'replace',
100
+ multiple: false,
101
+ docId: std.store.id,
102
+ })) ?? [];
103
+ return asset ? (attachmentPropsFrom([asset])[0] ?? null) : null;
104
+ }
105
+ const file = await openSingleFileWith();
106
+ if (!file)
107
+ return null;
108
+ const sourceId = await std.store.blobSync.set(file);
109
+ const type = await getFileType(file);
110
+ return { sourceId, name: file.name, type, size: file.size };
111
+ }
@@ -39,7 +39,7 @@ import { ResourceController, } from '../affine-components/resource/index.js';
39
39
  import { toast } from '../affine-components/toast/index.js';
40
40
  import { AttachmentBlockStyles, } from '../affine-model/index.js';
41
41
  import { BlockCommentManager, CitationProvider, DocModeProvider, FileSizeLimitProvider, TelemetryProvider, TOAST_COPIED_TO_CLIPBOARD, translateKey, } from '../affine-shared/services/index.js';
42
- import { formatSize, openSingleFileWith } from '../affine-shared/utils/index.js';
42
+ import { formatSize } from '../affine-shared/utils/index.js';
43
43
  import { AttachmentIcon, ResetIcon, UpgradeIcon, WarningIcon, } from '@blocksuite/icons/lit';
44
44
  import { BlockSelection } from '../std/index.js';
45
45
  import { nanoid, Slice } from '../store/index.js';
@@ -51,9 +51,10 @@ import { guard } from 'lit/directives/guard.js';
51
51
  import { styleMap } from 'lit/directives/style-map.js';
52
52
  import { when } from 'lit/directives/when.js';
53
53
  import { filter } from 'rxjs/operators';
54
+ import { pickAttachmentSource } from './asset-picker.js';
54
55
  import { AttachmentEmbedProvider } from './embed.js';
55
56
  import { styles } from './styles.js';
56
- import { downloadAttachmentBlob, getFileType, refreshData } from './utils.js';
57
+ import { downloadAttachmentBlob, refreshData } from './utils.js';
57
58
  import { ATTACHMENT_BUTTON_RELOAD, ATTACHMENT_BUTTON_RETRY, ATTACHMENT_BUTTON_UPGRADE, } from './translations.js';
58
59
  let AttachmentBlockComponent = (() => {
59
60
  let _classDecorators = [Peekable({
@@ -121,12 +122,13 @@ let AttachmentBlockComponent = (() => {
121
122
  const state = this.resourceController.state$.peek();
122
123
  if (state.uploading)
123
124
  return;
124
- const file = await openSingleFileWith();
125
- if (!file)
125
+ const source = await pickAttachmentSource(this.std);
126
+ // The dialog is async: the card may be gone, or the page read-only, by now.
127
+ if (!source || this.std.store.readonly)
126
128
  return;
127
- const sourceId = await this.std.store.blobSync.set(file);
128
- const type = await getFileType(file);
129
- const { name, size } = file;
129
+ if (!this.std.store.getBlock(this.blockId))
130
+ return;
131
+ const { name, size, type, sourceId } = source;
130
132
  let embed = this.model.props.embed$.value ?? false;
131
133
  this.std.store.captureSync();
132
134
  this.std.store.transact(() => {
@@ -27,9 +27,9 @@ import { databaseBlockStyles, databaseContentStyles, databaseHeaderBarStyles, da
27
27
  import { BlockRenderer } from './detail-panel/block-renderer.js';
28
28
  import { NoteRenderer } from './detail-panel/note-renderer.js';
29
29
  import { DatabaseSelection } from './selection.js';
30
- import { databaseOpenRowRequest, DatabaseDataSourceProvider, DatabaseNewRowMenuProvider, DatabaseOpenRowProvider, openDatabasePropertiesPanel, rowOpenedParams, } from './service/index.js';
30
+ import { databaseOpenRowRequest, DatabaseDataSourceProvider, DatabaseInitialViewProvider, DatabaseNewRowMenuProvider, DatabaseOpenRowProvider, openDatabasePropertiesPanel, rowOpenedParams, } from './service/index.js';
31
31
  import { DATABASE_NEW_RECORD_MORE, DATABASE_SOURCE_MISSING, DATABASE_SOURCE_MISSING_HINT, } from './translations.js';
32
- import { currentViewStorage } from './utils/current-view.js';
32
+ import { currentViewStorage, initialViewId } from './utils/current-view.js';
33
33
  import { getSingleDocIdFromText } from './utils/title-doc.js';
34
34
  import { DatabaseViewCreationIdentifier } from './view-creation.js';
35
35
  export class DatabaseBlockComponent extends CaptionedBlockComponent {
@@ -311,10 +311,9 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent {
311
311
  const dataSource = provider && this.model.props.externalSourceId
312
312
  ? provider.createDataSource(this.model, initFn)
313
313
  : new DatabaseBlockDataSource(this.model, initFn);
314
- const id = currentViewStorage.getCurrentView(this.model.id);
315
- if (id && dataSource.viewManager.viewGet(id)) {
314
+ const id = initialViewId(this.model.id, viewId => dataSource.viewManager.viewGet(viewId) != null, this.std.getOptional(DatabaseInitialViewProvider));
315
+ if (id)
316
316
  dataSource.viewManager.setCurrentView(id);
317
- }
318
317
  return dataSource;
319
318
  }
320
319
  get dataSource() {
@@ -14,6 +14,7 @@ export interface DatabaseDataSourceProvider {
14
14
  createDataSource(model: DatabaseBlockModel, init?: (ds: DataSourceBase) => void): DataSourceBase;
15
15
  }
16
16
  export declare const DatabaseDataSourceProvider: import("../../global/di/types.js").ServiceIdentifier<DatabaseDataSourceProvider> & (<U extends DatabaseDataSourceProvider = DatabaseDataSourceProvider>(variant: import("../../global/di/types.js").ServiceVariant) => import("../../global/di/types.js").ServiceIdentifier<U>);
17
+ export * from './initial-view.js';
17
18
  export * from './open-row.js';
18
19
  export * from './properties-panel.js';
19
20
  export * from './new-row-menu.js';
@@ -1,6 +1,7 @@
1
1
  import { createIdentifier } from '../../global/di/index.js';
2
2
  export const DatabaseBlockConfigService = createIdentifier('AffineDatabaseBlockConfigService');
3
3
  export const DatabaseDataSourceProvider = createIdentifier('affine:database-data-source-provider');
4
+ export * from './initial-view.js';
4
5
  export * from './open-row.js';
5
6
  export * from './properties-panel.js';
6
7
  export * from './new-row-menu.js';
@@ -0,0 +1,33 @@
1
+ import type { ExtensionType } from '../../store/index.js';
2
+ /**
3
+ * Which `affine:database` block asks for the view it opens on. Only widens:
4
+ * a new field is optional.
5
+ */
6
+ export interface DatabaseInitialViewRequest {
7
+ blockId: string;
8
+ }
9
+ /**
10
+ * The view a database opens on, chosen by the host (labre: a base's page
11
+ * opened from one of its views in the sidebar, `/base/:id?view=…`). Asked
12
+ * once, when the block builds its data source. An id the block does not
13
+ * have, or `undefined`, falls back to the view this tab last showed for the
14
+ * block (`currentViewStorage`), then to the first view.
15
+ *
16
+ * Nothing is written: the current view is a local signal of the view
17
+ * manager, never a stored prop, so opening on a view leaves the document
18
+ * untouched.
19
+ */
20
+ export interface DatabaseInitialViewService {
21
+ initialView(request: DatabaseInitialViewRequest): string | undefined;
22
+ }
23
+ export declare const DatabaseInitialViewProvider: import("../../global/di/types.js").ServiceIdentifier<DatabaseInitialViewService> & (<U extends DatabaseInitialViewService = DatabaseInitialViewService>(variant: import("../../global/di/types.js").ServiceVariant) => import("../../global/di/types.js").ServiceIdentifier<U>);
24
+ /**
25
+ * Host seam: the view a database opens on.
26
+ *
27
+ * Absent or `null`, a database opens on the view this tab last showed for
28
+ * it, else its first view, as in 0.59.
29
+ *
30
+ * `di.override`, so a later registration wins and registering twice never
31
+ * throws at boot.
32
+ */
33
+ export declare function DatabaseInitialViewExtension(service: DatabaseInitialViewService | null): ExtensionType;
@@ -0,0 +1,20 @@
1
+ import { createIdentifier } from '../../global/di/index.js';
2
+ export const DatabaseInitialViewProvider = createIdentifier('LabreDatabaseInitialView');
3
+ /**
4
+ * Host seam: the view a database opens on.
5
+ *
6
+ * Absent or `null`, a database opens on the view this tab last showed for
7
+ * it, else its first view, as in 0.59.
8
+ *
9
+ * `di.override`, so a later registration wins and registering twice never
10
+ * throws at boot.
11
+ */
12
+ export function DatabaseInitialViewExtension(service) {
13
+ return {
14
+ setup: di => {
15
+ // `null` rides through on purpose: `getOptional` then reports no host
16
+ // choice and the block keeps its own.
17
+ di.override(DatabaseInitialViewProvider, () => service);
18
+ },
19
+ };
20
+ }
@@ -1,4 +1,11 @@
1
+ import type { DatabaseInitialViewService } from '../service/initial-view.js';
1
2
  export declare const currentViewStorage: {
2
3
  getCurrentView: (blockId: string) => string | undefined;
3
4
  setCurrentView: (blockId: string, viewId: string) => void;
4
5
  };
6
+ /**
7
+ * The view a database opens on: the host's choice when the block has it
8
+ * (`DatabaseInitialViewProvider`), else the one this tab last showed, else
9
+ * `undefined` (the view manager's first view). Reads only.
10
+ */
11
+ export declare function initialViewId(blockId: string, exists: (viewId: string) => boolean, host?: DatabaseInitialViewService | null): string | undefined;
@@ -45,3 +45,17 @@ const createCurrentViewStorage = () => {
45
45
  };
46
46
  };
47
47
  export const currentViewStorage = createCurrentViewStorage();
48
+ /**
49
+ * The view a database opens on: the host's choice when the block has it
50
+ * (`DatabaseInitialViewProvider`), else the one this tab last showed, else
51
+ * `undefined` (the view manager's first view). Reads only.
52
+ */
53
+ export function initialViewId(blockId, exists, host) {
54
+ const asked = host?.initialView({ blockId });
55
+ if (asked && exists(asked))
56
+ return asked;
57
+ const stored = currentViewStorage.getCurrentView(blockId);
58
+ if (stored && exists(stored))
59
+ return stored;
60
+ return undefined;
61
+ }
@@ -1,5 +1,6 @@
1
1
  import { getSurfaceBlock } from '../../affine-block-surface/index.js';
2
2
  import { ListBlockModel, NoteBlockModel, NoteDisplayMode, ParagraphBlockModel, } from '../../affine-model/index.js';
3
+ import { LINKED_DOC_NOTE_STYLE } from '../../affine-shared/consts/index.js';
3
4
  import { CHROME_UNTITLED, NotificationProvider, TOAST_LINKED_DOC_CREATED, TOOLBAR_CREATE_LINKED_DOC, translateKey, } from '../../affine-shared/services/index.js';
4
5
  import { matchModels } from '../../affine-shared/utils/index.js';
5
6
  import { Slice, Text, } from '../../store/index.js';
@@ -125,6 +126,7 @@ export async function convertSelectedBlocksToLinkedDoc(std, doc, selectedModels,
125
126
  {
126
127
  flavour: 'affine:embed-linked-doc',
127
128
  pageId: linkedDoc.id,
129
+ style: LINKED_DOC_NOTE_STYLE,
128
130
  },
129
131
  ], 'before');
130
132
  // delete selected elements
@@ -0,0 +1,25 @@
1
+ import type { ReferenceParams } from '../../affine-model/index.js';
2
+ import type { BlockStdScope } from '../../std/index.js';
3
+ import type { TemplateResult } from 'lit';
4
+ type CardStd = Pick<BlockStdScope, 'get' | 'getOptional'>;
5
+ /**
6
+ * Whether a card of page `pageId` held by document `docId` is the child's own
7
+ * card rather than a reference (labre ADR-0045, amended 2026-10-27): the host
8
+ * lists the document's children through `TitleBandService.children$`. The
9
+ * library never computes a parent or a child. Read inside a `computed`, so a
10
+ * move in the host's tree redraws the card. Without the seam, or without
11
+ * `children$`, every card is a reference, as in 0.59.
12
+ */
13
+ export declare function isChildCard(std: Pick<CardStd, 'getOptional'>, docId: string, pageId: string): boolean;
14
+ /**
15
+ * The icon of a card (`embed-linked-doc`, `embed-synced-doc`): a child's own
16
+ * card shows the plain page or canvas icon; any other card, like an inline
17
+ * `@` reference, keeps the linked icon with its arrow (product owner,
18
+ * recette 3 P2-7c). An alias or a block link keeps its own icon.
19
+ */
20
+ export declare function cardIcon(std: CardStd, docId: string, info: {
21
+ pageId: string;
22
+ params?: ReferenceParams;
23
+ title?: string;
24
+ }): TemplateResult;
25
+ export {};
@@ -0,0 +1,27 @@
1
+ import { DocDisplayMetaProvider, TitleBandProvider, } from '../../affine-shared/services/index.js';
2
+ /**
3
+ * Whether a card of page `pageId` held by document `docId` is the child's own
4
+ * card rather than a reference (labre ADR-0045, amended 2026-10-27): the host
5
+ * lists the document's children through `TitleBandService.children$`. The
6
+ * library never computes a parent or a child. Read inside a `computed`, so a
7
+ * move in the host's tree redraws the card. Without the seam, or without
8
+ * `children$`, every card is a reference, as in 0.59.
9
+ */
10
+ export function isChildCard(std, docId, pageId) {
11
+ const children$ = std.getOptional(TitleBandProvider)?.children$?.(docId);
12
+ return children$?.value.includes(pageId) ?? false;
13
+ }
14
+ /**
15
+ * The icon of a card (`embed-linked-doc`, `embed-synced-doc`): a child's own
16
+ * card shows the plain page or canvas icon; any other card, like an inline
17
+ * `@` reference, keeps the linked icon with its arrow (product owner,
18
+ * recette 3 P2-7c). An alias or a block link keeps its own icon.
19
+ */
20
+ export function cardIcon(std, docId, info) {
21
+ const { pageId, params, title } = info;
22
+ return std.get(DocDisplayMetaProvider).icon(pageId, {
23
+ params,
24
+ title,
25
+ referenced: !isChildCard(std, docId, pageId),
26
+ }).value;
27
+ }
@@ -1,9 +1,14 @@
1
1
  import { insertEmbedCard } from '../../../affine-block-embed/index.js';
2
+ import { LINKED_DOC_NOTE_STYLE } from '../../../affine-shared/consts/index.js';
2
3
  export const insertEmbedLinkedDocCommand = (ctx, next) => {
3
4
  const { docId, params, std } = ctx;
4
5
  const flavour = 'affine:embed-linked-doc';
5
6
  const targetStyle = 'vertical';
6
- const props = { pageId: docId };
7
+ // In a note the card is a line; on the canvas `targetStyle` wins.
8
+ const props = {
9
+ pageId: docId,
10
+ style: LINKED_DOC_NOTE_STYLE,
11
+ };
7
12
  if (params)
8
13
  props.params = params;
9
14
  const blockId = insertEmbedCard(std, { flavour, targetStyle, props });
@@ -1,5 +1,5 @@
1
1
  import { insertContent } from '../../../affine-rich-text/index.js';
2
- import { REFERENCE_NODE } from '../../../affine-shared/consts/index.js';
2
+ import { LINKED_DOC_NOTE_STYLE, REFERENCE_NODE, } from '../../../affine-shared/consts/index.js';
3
3
  import { LinkedDocCreationProvider, TelemetryProvider, } from '../../../affine-shared/services/index.js';
4
4
  import { createDefaultDoc, writeDefaultDocBlocks, } from '../../../affine-shared/utils/index.js';
5
5
  const LINKED_DOC = 'affine:embed-linked-doc';
@@ -63,7 +63,7 @@ function insertCard(store, anchor, pageId) {
63
63
  if (!parent || !store.schema.isValid(LINKED_DOC, parent.flavour)) {
64
64
  return null;
65
65
  }
66
- const cardId = store.addBlock(LINKED_DOC, { pageId }, parent, parent.children.indexOf(anchor) + 1);
66
+ const cardId = store.addBlock(LINKED_DOC, { pageId, style: LINKED_DOC_NOTE_STYLE }, parent, parent.children.indexOf(anchor) + 1);
67
67
  if (anchor.flavour === 'affine:paragraph' && anchor.text?.length === 0) {
68
68
  store.deleteBlock(anchor);
69
69
  }
@@ -54,6 +54,7 @@ import { when } from 'lit/directives/when.js';
54
54
  import throttle from 'lodash-es/throttle.js';
55
55
  import { filter } from 'rxjs/operators';
56
56
  import * as Y from 'yjs';
57
+ import { cardIcon } from '../common/child-card.js';
57
58
  import { isDocTrashed } from '../common/doc-trashed.js';
58
59
  import { renderLinkedDocInCard } from '../common/render-linked-doc.js';
59
60
  import { SyncedDocErrorIcon } from '../embed-synced-doc-block/styles.js';
@@ -371,12 +372,7 @@ let EmbedLinkedDocBlockComponent = (() => {
371
372
  description: description$.value,
372
373
  });
373
374
  });
374
- this.icon$ = computed(() => {
375
- const { pageId, params, title } = this.referenceInfo$.value;
376
- return this.std
377
- .get(DocDisplayMetaProvider)
378
- .icon(pageId, { params, title, referenced: true }).value;
379
- });
375
+ this.icon$ = computed(() => cardIcon(this.std, this.store.id, this.referenceInfo$.value));
380
376
  this.open = ({ openMode, event, } = {}) => {
381
377
  this.std.getOptional(RefNodeSlotsProvider)?.docLinkClicked.next({
382
378
  ...this.referenceInfo$.peek(),
@@ -52,6 +52,7 @@ import { classMap } from 'lit/directives/class-map.js';
52
52
  import { guard } from 'lit/directives/guard.js';
53
53
  import { styleMap } from 'lit/directives/style-map.js';
54
54
  import * as Y from 'yjs';
55
+ import { cardIcon } from '../common/child-card.js';
55
56
  import { isDocTrashed } from '../common/doc-trashed.js';
56
57
  import { EMBED_DOC_CARD_EMPTY_CONTENT } from '../translations.js';
57
58
  import { blockStyles } from './styles.js';
@@ -329,9 +330,7 @@ let EmbedSyncedDocBlockComponent = (() => {
329
330
  };
330
331
  this.icon$ = computed(() => {
331
332
  const { pageId, params } = this.model.props;
332
- return this.std
333
- .get(DocDisplayMetaProvider)
334
- .icon(pageId, { params, referenced: true }).value;
333
+ return cardIcon(this.std, this.store.id, { pageId, params });
335
334
  });
336
335
  this.open = (event) => {
337
336
  const pageId = this.model.props.pageId;
@@ -1,5 +1,5 @@
1
1
  import { EdgelessCRUDIdentifier } from '../../affine-block-surface/index.js';
2
- import { EMBED_CARD_HEIGHT, EMBED_CARD_WIDTH, } from '../../affine-shared/consts/index.js';
2
+ import { EMBED_CARD_HEIGHT, EMBED_CARD_WIDTH, LINKED_DOC_NOTE_STYLE, } from '../../affine-shared/consts/index.js';
3
3
  import { PageDropProvider, pageDropVerdict, TelemetryProvider, } from '../../affine-shared/services/index.js';
4
4
  import { calcDropTarget, getClosestBlockComponentByPoint, getLastNoteBlock, isInsideEdgelessEditor, matchModels, } from '../../affine-shared/utils/index.js';
5
5
  import { Bound, Point } from '../../global/gfx/index.js';
@@ -52,7 +52,7 @@ function insertBeside(store, pageId, line) {
52
52
  if (!parent)
53
53
  return null;
54
54
  if (store.schema.safeValidate(LINKED_DOC, parent.flavour)) {
55
- return (store.addSiblingBlocks(model, [{ flavour: LINKED_DOC, pageId }], placement)[0] ?? null);
55
+ return (store.addSiblingBlocks(model, [{ flavour: LINKED_DOC, pageId, style: LINKED_DOC_NOTE_STYLE }], placement)[0] ?? null);
56
56
  }
57
57
  // Out of a list item or a callout: the card goes after the container.
58
58
  model = parent;
@@ -65,7 +65,7 @@ function insertAtEnd(store, pageId) {
65
65
  if (!root)
66
66
  return null;
67
67
  const noteId = getLastNoteBlock(store)?.id ?? store.addBlock('affine:note', {}, root.id);
68
- return store.addBlock(LINKED_DOC, { pageId }, noteId);
68
+ return store.addBlock(LINKED_DOC, { pageId, style: LINKED_DOC_NOTE_STYLE }, noteId);
69
69
  }
70
70
  function insertOnCanvas(std, pageId, point) {
71
71
  const gfx = std.get(GfxControllerIdentifier);
@@ -10,6 +10,7 @@ export * from './element-renderer.js';
10
10
  export * from './export-manager/index.js';
11
11
  export * from './export-svg/index.js';
12
12
  export * from './interchange.js';
13
+ export * from './toolbox.js';
13
14
  export * from './interchange-export.js';
14
15
  export * from './interchange-import.js';
15
16
  export * from './legacy-slot-extension.js';
@@ -10,6 +10,7 @@ export * from './element-renderer.js';
10
10
  export * from './export-manager/index.js';
11
11
  export * from './export-svg/index.js';
12
12
  export * from './interchange.js';
13
+ export * from './toolbox.js';
13
14
  export * from './interchange-export.js';
14
15
  export * from './interchange-import.js';
15
16
  export * from './legacy-slot-extension.js';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The ink and the type a notation writes its labels in: the legend box's rows,
3
+ * and the words the DDD prefabs set beside a dot or a marker.
4
+ *
5
+ * Declared HERE and re-exported by `@labre/affine-gfx-ddd-shared`, rather than
6
+ * the other way round, because that bundle depends on this one and a legend
7
+ * must not depend on a DDD bundle to be drawn.
8
+ */
9
+ export declare const LABEL_COLOR: string;
10
+ export declare const LABEL_FONT = "blocksuite:surface:Inter";
11
+ export declare const LABEL_FONT_SIZE = 14;
@@ -0,0 +1,16 @@
1
+ import { NOTATION_NEUTRALS } from '../../affine-shared/headless.js';
2
+ // Its own module so the headless interchange entry (`./interchange`) can read
3
+ // it without the legend's editor graph: the DDD element builders of
4
+ // `@labre/affine-gfx-ddd-shared/headless` write their captions in these three
5
+ // values (`docs/adr/0045`). `legend.ts` re-exports them unchanged.
6
+ /**
7
+ * The ink and the type a notation writes its labels in: the legend box's rows,
8
+ * and the words the DDD prefabs set beside a dot or a marker.
9
+ *
10
+ * Declared HERE and re-exported by `@labre/affine-gfx-ddd-shared`, rather than
11
+ * the other way round, because that bundle depends on this one and a legend
12
+ * must not depend on a DDD bundle to be drawn.
13
+ */
14
+ export const LABEL_COLOR = NOTATION_NEUTRALS.ink;
15
+ export const LABEL_FONT = 'blocksuite:surface:Inter';
16
+ export const LABEL_FONT_SIZE = 14;
@@ -46,17 +46,7 @@ export interface LegendLayout {
46
46
  }
47
47
  /** A block of the box that is not a row. See {@link CommandLegendBox.extras}. */
48
48
  export type LegendExtra = CommandLegendExtra;
49
- /**
50
- * The ink and the type a notation writes its labels in: this box's rows, and
51
- * the words the DDD prefabs set beside a dot or a marker.
52
- *
53
- * Declared HERE and re-exported by `@labre/affine-gfx-ddd-shared`, rather than
54
- * the other way round, because that bundle depends on this one and a legend
55
- * must not depend on a DDD bundle to be drawn.
56
- */
57
- export declare const LABEL_COLOR: string;
58
- export declare const LABEL_FONT = "blocksuite:surface:Inter";
59
- export declare const LABEL_FONT_SIZE = 14;
49
+ export { LABEL_COLOR, LABEL_FONT, LABEL_FONT_SIZE } from './legend-label.js';
60
50
  export { LEGEND_ROLE };
61
51
  /**
62
52
  * How big the box {@link addLegend} would draw for these sections is — without
@@ -5,18 +5,9 @@ import { Bound } from '../../global/gfx/index.js';
5
5
  import { CATALOGUE_CATEGORY_KEY_PREFIX, getRegisteredCommands, humanizeCategory, } from '../../std/index.js';
6
6
  import { GfxControllerIdentifier, roleIsA, RoleVocabularyIdentifier, } from '../../std/gfx/index.js';
7
7
  import { EdgelessCRUDIdentifier } from './crud-extension.js';
8
+ import { LABEL_COLOR, LABEL_FONT, LABEL_FONT_SIZE } from './legend-label.js';
8
9
  import { LEGEND_ROLE } from './legend-role.js';
9
- /**
10
- * The ink and the type a notation writes its labels in: this box's rows, and
11
- * the words the DDD prefabs set beside a dot or a marker.
12
- *
13
- * Declared HERE and re-exported by `@labre/affine-gfx-ddd-shared`, rather than
14
- * the other way round, because that bundle depends on this one and a legend
15
- * must not depend on a DDD bundle to be drawn.
16
- */
17
- export const LABEL_COLOR = NOTATION_NEUTRALS.ink;
18
- export const LABEL_FONT = 'blocksuite:surface:Inter';
19
- export const LABEL_FONT_SIZE = 14;
10
+ export { LABEL_COLOR, LABEL_FONT, LABEL_FONT_SIZE } from './legend-label.js';
20
11
  const NO_STROKE = '#00000000';
21
12
  /**
22
13
  * The box's geometry, in one place because two callers need it: the one that
@@ -0,0 +1,62 @@
1
+ import type { SerializedElementProps } from './interchange.js';
2
+ /**
3
+ * A framework's TOOLBOX, with no editor behind it (`docs/adr/0045`): every
4
+ * item of its toolbar as a pure builder, so a host with no editor — the Labre
5
+ * workspace connector, labre-mcp — places the very elements a user places
6
+ * from the toolbar, never shapes that imitate them.
7
+ *
8
+ * ## The same contract as a reader's result
9
+ *
10
+ * A builder returns what an import capability returns (ADR 0012 D3): element
11
+ * props in painting order, each composite's members before the group that
12
+ * holds them, and LOCAL `id`s the group's `children` and a link's ends name.
13
+ * `surface.addElement` mints the real ids and the materializer
14
+ * (`materializeInterchangeImport`) rewrites the references, so one writer
15
+ * serves a whole file read by a reader and one item built here.
16
+ *
17
+ * ## What a builder does not do
18
+ *
19
+ * It places ONE item at the point it is given: where things go on a board is
20
+ * the caller's. It writes the texts it is given, or the English seed: a seed
21
+ * in the host's language is resolved by the editor's creation site, which has
22
+ * the catalogue (ADR 0023). It persists nothing.
23
+ */
24
+ /** Where an item lands: the top-left of its box, in canvas units. */
25
+ export interface ToolboxPoint {
26
+ x: number;
27
+ y: number;
28
+ }
29
+ /** One toolbar item that places elements. */
30
+ export interface ToolboxItem {
31
+ /** Stable within the framework, kebab-case: `container`, `system-boundary`. */
32
+ readonly id: string;
33
+ /** The toolbar command's own label, so a host names it as the toolbar does. */
34
+ readonly labelKey: string;
35
+ readonly labelFallback: string;
36
+ /** The text slots `build` fills, in reading order (`title`, `description`…). */
37
+ readonly texts: readonly string[];
38
+ /** The box the item is born with. */
39
+ readonly size: {
40
+ readonly w: number;
41
+ readonly h: number;
42
+ };
43
+ /**
44
+ * The elements, with local ids and the group last. `local` prefixes those
45
+ * ids, so two items built for one batch never share a name.
46
+ */
47
+ build(at: ToolboxPoint, texts?: Readonly<Record<string, string>>, local?: string): SerializedElementProps[];
48
+ }
49
+ /** One relation style of the framework: a connector between two elements. */
50
+ export interface ToolboxLink {
51
+ readonly id: string;
52
+ readonly labelKey: string;
53
+ readonly labelFallback: string;
54
+ /** The connector props; `from` and `to` are element ids or local ids. */
55
+ build(from: string, to: string, label?: string): SerializedElementProps;
56
+ }
57
+ export interface FrameworkToolbox {
58
+ /** The framework's descriptor id (`c4`, `bpmn`…). */
59
+ readonly framework: string;
60
+ readonly items: readonly ToolboxItem[];
61
+ readonly links: readonly ToolboxLink[];
62
+ }
@@ -18,6 +18,8 @@
18
18
  */
19
19
  export * from './extensions/export-svg/marker-version.js';
20
20
  export * from './extensions/interchange.js';
21
+ export * from './extensions/toolbox.js';
22
+ export * from './extensions/legend-label.js';
21
23
  export * from './extensions/legend-role.js';
22
24
  export * from './extensions/svg-sanitize.js';
23
25
  export * from './extensions/svg-sketch.js';
@@ -18,6 +18,8 @@
18
18
  */
19
19
  export * from './extensions/export-svg/marker-version.js';
20
20
  export * from './extensions/interchange.js';
21
+ export * from './extensions/toolbox.js';
22
+ export * from './extensions/legend-label.js';
21
23
  export * from './extensions/legend-role.js';
22
24
  export * from './extensions/svg-sanitize.js';
23
25
  export * from './extensions/svg-sketch.js';
@@ -1,3 +1,9 @@
1
+ /**
2
+ * The library's spinner. Rotated by SMIL, not by a `<style>` inside the
3
+ * SVG: that sheet was global in light DOM (any `.spinner` of the page
4
+ * spun), and a binding after it fails to render under happy-dom, so no
5
+ * unit spec could mount a view that draws it.
6
+ */
1
7
  export declare const LoadingIcon: ({ size, progress, ringColor, strokeColor, }?: {
2
8
  size?: string;
3
9
  progress?: number;