From c0e1b6d5be2f0911cb6ea7398a89dc033082c77f Mon Sep 17 00:00:00 2001 From: Lucas Law Date: Wed, 15 Jul 2026 13:21:53 +0800 Subject: [PATCH 01/13] =?UTF-8?q?Table=20Seating=20Planner:=20UX=20?= =?UTF-8?q?=E2=80=94=20undo/redo=20buttons=20+=20sidebar=20dedup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit linear: https://linear.app/cardstack/issue/CS-12106 ## summary: UX fixes from the PM UX evaluation (CS-11949): - Surface visible Undo/Redo buttons in the canvas toolbar, wired to the existing undo/redo stacks and disabled via undoDepth/redoDepth. The ⌘Z / β‡§βŒ˜Z shortcuts already existed; this adds the missing visible affordance ("No redo/undo action"). - Dedupe the guest rail header: drop the redundant standalone total and the "All guests seated" banner (and the now-unused getter/CSS), keeping one "Guests Β· X of Y seated" line + progress bar ("sidebar information seems duplicate"). Action-hierarchy styling and Boxel Button/theme reuse are intentionally left to the UI refactor (CS-12101). --- .../components/tsp.gts | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/41e20f-wedding-table-seating-planner/components/tsp.gts b/41e20f-wedding-table-seating-planner/components/tsp.gts index 6eda372b..43ed0330 100644 --- a/41e20f-wedding-table-seating-planner/components/tsp.gts +++ b/41e20f-wedding-table-seating-planner/components/tsp.gts @@ -46,6 +46,8 @@ import ArrowsMoveIcon from '@cardstack/boxel-icons/arrows-move'; import TemplateIcon from '@cardstack/boxel-icons/template'; import RefreshIcon from '@cardstack/boxel-icons/refresh'; import FloorPlanIcon from '@cardstack/boxel-icons/floor-plan'; +import ArrowBackUpIcon from '@cardstack/boxel-icons/arrow-back-up'; +import ArrowForwardUpIcon from '@cardstack/boxel-icons/arrow-forward-up'; import { FIXTURE_KINDS, FIXTURE_KIND_LABELS, @@ -551,12 +553,6 @@ export class TableSeatingPlannerIsolated extends Component< } return s; } - get allRosterSeated(): boolean { - return ( - this.guests.length > 0 && - this.guests.every((g) => this.seatedGuestSet.has(g as Guest)) - ); - } get totalGuests() { try { return this.guests.length; @@ -3935,7 +3931,6 @@ export class TableSeatingPlannerIsolated extends Component<
+
+ + +
+
@@ -5206,7 +5206,7 @@ export class TableSeatingPlannerIsolated extends Component<
Arrange the room
- Seated + Assigned {{this.seatedCount}} of {{this.totalGuests}} @@ -5663,7 +5663,7 @@ export class TableSeatingPlannerIsolated extends Component<
{{this.selectedTable.seatedCount}} of {{this.selectedTable.seatCount}} - seated
+ assigned
<:body> @@ -9904,7 +9904,7 @@ export class TableSeatingPlannerFitted extends Component< {{@model.tables.length}} tables {{this.seatedCount}}/{{@model.guests.length}} - seated + assigned
diff --git a/41e20f-wedding-table-seating-planner/table.gts b/41e20f-wedding-table-seating-planner/table.gts index 082e13a2..86f59ba9 100644 --- a/41e20f-wedding-table-seating-planner/table.gts +++ b/41e20f-wedding-table-seating-planner/table.gts @@ -342,7 +342,7 @@ export class Table extends FieldDef { {{/if}} {{#if @model.seatedGuests.length}}
-

Seated

+

Assigned

<@fields.seatedGuests @format='embedded' />
{{/if}} From e57c0af2b74440d5e4f39035471e445aaef494b8 Mon Sep 17 00:00:00 2001 From: Lucas Law Date: Wed, 15 Jul 2026 13:54:45 +0800 Subject: [PATCH 03/13] Table Seating Planner: add PlaceCard & TableCard wedding stationery linear: https://linear.app/cardstack/issue/CS-12106 ## summary: Add two printable wedding-stationery card types produced from the seating plan (PM UX evaluation, CS-11949: "Place Card, Table Card for wedding"): - PlaceCard (place-card.gts): links a Guest and shows their name, table, and event title in a themed, print-sized card (isolated + fitted formats). - TableCard (table-card.gts): a large table name/number sign with the event title. Both use --pc-*/--tc-* design tokens defaulting to the burgundy/gold theme. Adds example instances (PlaceCard/andy, PlaceCard/nana, TableCard/rose) and wires them into the CardListing examples. --- .../6241aa1f-0a8a-45f3-9b8b-57ac600070c4.json | 15 ++ .../PlaceCard/andy.json | 39 ++++ .../PlaceCard/nana.json | 38 ++++ .../TableCard/rose.json | 34 ++++ .../place-card.gts | 176 ++++++++++++++++++ .../table-card.gts | 149 +++++++++++++++ 6 files changed, 451 insertions(+) create mode 100644 41e20f-wedding-table-seating-planner/PlaceCard/andy.json create mode 100644 41e20f-wedding-table-seating-planner/PlaceCard/nana.json create mode 100644 41e20f-wedding-table-seating-planner/TableCard/rose.json create mode 100644 41e20f-wedding-table-seating-planner/place-card.gts create mode 100644 41e20f-wedding-table-seating-planner/table-card.gts diff --git a/41e20f-wedding-table-seating-planner/CardListing/6241aa1f-0a8a-45f3-9b8b-57ac600070c4.json b/41e20f-wedding-table-seating-planner/CardListing/6241aa1f-0a8a-45f3-9b8b-57ac600070c4.json index 07f85957..a5be2eeb 100644 --- a/41e20f-wedding-table-seating-planner/CardListing/6241aa1f-0a8a-45f3-9b8b-57ac600070c4.json +++ b/41e20f-wedding-table-seating-planner/CardListing/6241aa1f-0a8a-45f3-9b8b-57ac600070c4.json @@ -263,6 +263,21 @@ "self": "../../index" } }, + "examples.18": { + "links": { + "self": "../PlaceCard/andy" + } + }, + "examples.19": { + "links": { + "self": "../PlaceCard/nana" + } + }, + "examples.20": { + "links": { + "self": "../TableCard/rose" + } + }, "categories.0": { "links": { "self": "../../Category/creative-projects" diff --git a/41e20f-wedding-table-seating-planner/PlaceCard/andy.json b/41e20f-wedding-table-seating-planner/PlaceCard/andy.json new file mode 100644 index 00000000..6db43acb --- /dev/null +++ b/41e20f-wedding-table-seating-planner/PlaceCard/andy.json @@ -0,0 +1,39 @@ +{ + "data": { + "type": "card", + "attributes": { + "tableName": "Table 1 Β· Rose", + "eventTitle": "Lucas & Amy", + "message": "With love", + "cardInfo": { + "name": null, + "notes": null, + "summary": null, + "cardThumbnailURL": null + } + }, + "relationships": { + "guest": { + "links": { + "self": "../Guest/andy" + } + }, + "cardInfo.theme": { + "links": { + "self": null + } + }, + "cardInfo.cardThumbnail": { + "links": { + "self": null + } + } + }, + "meta": { + "adoptsFrom": { + "name": "PlaceCard", + "module": "../place-card" + } + } + } +} diff --git a/41e20f-wedding-table-seating-planner/PlaceCard/nana.json b/41e20f-wedding-table-seating-planner/PlaceCard/nana.json new file mode 100644 index 00000000..85607743 --- /dev/null +++ b/41e20f-wedding-table-seating-planner/PlaceCard/nana.json @@ -0,0 +1,38 @@ +{ + "data": { + "type": "card", + "attributes": { + "tableName": "Table 3 Β· Peony", + "eventTitle": "Lucas & Amy", + "cardInfo": { + "name": null, + "notes": null, + "summary": null, + "cardThumbnailURL": null + } + }, + "relationships": { + "guest": { + "links": { + "self": "../Guest/nana" + } + }, + "cardInfo.theme": { + "links": { + "self": null + } + }, + "cardInfo.cardThumbnail": { + "links": { + "self": null + } + } + }, + "meta": { + "adoptsFrom": { + "name": "PlaceCard", + "module": "../place-card" + } + } + } +} diff --git a/41e20f-wedding-table-seating-planner/TableCard/rose.json b/41e20f-wedding-table-seating-planner/TableCard/rose.json new file mode 100644 index 00000000..6a506c31 --- /dev/null +++ b/41e20f-wedding-table-seating-planner/TableCard/rose.json @@ -0,0 +1,34 @@ +{ + "data": { + "type": "card", + "attributes": { + "tableName": "Table 1", + "eventTitle": "Lucas & Amy", + "accent": "Rose", + "cardInfo": { + "name": null, + "notes": null, + "summary": null, + "cardThumbnailURL": null + } + }, + "relationships": { + "cardInfo.theme": { + "links": { + "self": null + } + }, + "cardInfo.cardThumbnail": { + "links": { + "self": null + } + } + }, + "meta": { + "adoptsFrom": { + "name": "TableCard", + "module": "../table-card" + } + } + } +} diff --git a/41e20f-wedding-table-seating-planner/place-card.gts b/41e20f-wedding-table-seating-planner/place-card.gts new file mode 100644 index 00000000..d632db34 --- /dev/null +++ b/41e20f-wedding-table-seating-planner/place-card.gts @@ -0,0 +1,176 @@ +import { + CardDef, + Component, + field, + contains, + linksTo, +} from 'https://cardstack.com/base/card-api'; +import StringField from 'https://cardstack.com/base/string'; + +import { Guest } from './guest'; + +// A printable wedding place card: the guest's name, their table, and the +// event title, styled to match the planner's burgundy/gold theme. The fitted +// format is sized for print (standard tented place-card proportions). +export class PlaceCard extends CardDef { + static displayName = 'Place Card'; + + @field guest = linksTo(() => Guest); + @field tableName = contains(StringField); + @field eventTitle = contains(StringField); + @field message = contains(StringField); + + @field guestName = contains(StringField, { + computeVia: function (this: PlaceCard) { + return this.guest?.fullName?.trim() || 'Guest Name'; + }, + }); + + @field title = contains(StringField, { + computeVia: function (this: PlaceCard) { + return this.guest?.fullName?.trim() || 'Place Card'; + }, + }); + + static isolated = class Isolated extends Component { + + }; + + static fitted = class Fitted extends Component { + + }; +} diff --git a/41e20f-wedding-table-seating-planner/table-card.gts b/41e20f-wedding-table-seating-planner/table-card.gts new file mode 100644 index 00000000..3fbac7da --- /dev/null +++ b/41e20f-wedding-table-seating-planner/table-card.gts @@ -0,0 +1,149 @@ +import { + CardDef, + Component, + field, + contains, +} from 'https://cardstack.com/base/card-api'; +import StringField from 'https://cardstack.com/base/string'; + +// A printable wedding table card / sign: a large table name or number with the +// event title, styled to match the planner's burgundy/gold theme. +export class TableCard extends CardDef { + static displayName = 'Table Card'; + + @field tableName = contains(StringField); + @field eventTitle = contains(StringField); + @field accent = contains(StringField); + + @field title = contains(StringField, { + computeVia: function (this: TableCard) { + return this.tableName?.trim() || 'Table Card'; + }, + }); + + static isolated = class Isolated extends Component { + + }; + + static fitted = class Fitted extends Component { + + }; +} From b083c00a942bbc8081a38e2957c453a564245da3 Mon Sep 17 00:00:00 2001 From: Lucas Law Date: Wed, 15 Jul 2026 15:12:02 +0800 Subject: [PATCH 04/13] Table Seating Planner: print place/table cards from the plan; default table names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit linear: https://linear.app/cardstack/issue/CS-12106 ## summary: Replace the standalone PlaceCard/TableCard CardDefs with print views driven by the existing seating data, and add printing: - Add card-views.gts (PlaceCardView / TableCardView) β€” shared, container-query-sized stationery visuals. - "Print cards" toolbar button prints every table's card + its guests' place cards; a per-table print button in the table popover prints a single table. Uses the browser print dialog (Save as PDF). - Remove place-card.gts / table-card.gts CardDefs and their example instances, and revert the CardListing example wiring β€” a TableCard/PlaceCard would only duplicate Table (contained field) and Guest (already a card), so cards are rendered as views instead of stored, keeping the planner->containsMany(Table)->linksToMany(Guest) schema referentially safe. - Default new-table name is now " Table " using a global index (e.g. "Round Table 1", "Oval Table 2"). --- .../6241aa1f-0a8a-45f3-9b8b-57ac600070c4.json | 15 -- .../PlaceCard/andy.json | 39 ---- .../PlaceCard/nana.json | 38 ---- .../TableCard/rose.json | 34 ---- .../card-views.gts | 156 ++++++++++++++++ .../components/tsp.gts | 87 ++++++++- .../place-card.gts | 176 ------------------ .../table-card.gts | 149 --------------- 8 files changed, 241 insertions(+), 453 deletions(-) delete mode 100644 41e20f-wedding-table-seating-planner/PlaceCard/andy.json delete mode 100644 41e20f-wedding-table-seating-planner/PlaceCard/nana.json delete mode 100644 41e20f-wedding-table-seating-planner/TableCard/rose.json create mode 100644 41e20f-wedding-table-seating-planner/card-views.gts delete mode 100644 41e20f-wedding-table-seating-planner/place-card.gts delete mode 100644 41e20f-wedding-table-seating-planner/table-card.gts diff --git a/41e20f-wedding-table-seating-planner/CardListing/6241aa1f-0a8a-45f3-9b8b-57ac600070c4.json b/41e20f-wedding-table-seating-planner/CardListing/6241aa1f-0a8a-45f3-9b8b-57ac600070c4.json index a5be2eeb..07f85957 100644 --- a/41e20f-wedding-table-seating-planner/CardListing/6241aa1f-0a8a-45f3-9b8b-57ac600070c4.json +++ b/41e20f-wedding-table-seating-planner/CardListing/6241aa1f-0a8a-45f3-9b8b-57ac600070c4.json @@ -263,21 +263,6 @@ "self": "../../index" } }, - "examples.18": { - "links": { - "self": "../PlaceCard/andy" - } - }, - "examples.19": { - "links": { - "self": "../PlaceCard/nana" - } - }, - "examples.20": { - "links": { - "self": "../TableCard/rose" - } - }, "categories.0": { "links": { "self": "../../Category/creative-projects" diff --git a/41e20f-wedding-table-seating-planner/PlaceCard/andy.json b/41e20f-wedding-table-seating-planner/PlaceCard/andy.json deleted file mode 100644 index 6db43acb..00000000 --- a/41e20f-wedding-table-seating-planner/PlaceCard/andy.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "data": { - "type": "card", - "attributes": { - "tableName": "Table 1 Β· Rose", - "eventTitle": "Lucas & Amy", - "message": "With love", - "cardInfo": { - "name": null, - "notes": null, - "summary": null, - "cardThumbnailURL": null - } - }, - "relationships": { - "guest": { - "links": { - "self": "../Guest/andy" - } - }, - "cardInfo.theme": { - "links": { - "self": null - } - }, - "cardInfo.cardThumbnail": { - "links": { - "self": null - } - } - }, - "meta": { - "adoptsFrom": { - "name": "PlaceCard", - "module": "../place-card" - } - } - } -} diff --git a/41e20f-wedding-table-seating-planner/PlaceCard/nana.json b/41e20f-wedding-table-seating-planner/PlaceCard/nana.json deleted file mode 100644 index 85607743..00000000 --- a/41e20f-wedding-table-seating-planner/PlaceCard/nana.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "data": { - "type": "card", - "attributes": { - "tableName": "Table 3 Β· Peony", - "eventTitle": "Lucas & Amy", - "cardInfo": { - "name": null, - "notes": null, - "summary": null, - "cardThumbnailURL": null - } - }, - "relationships": { - "guest": { - "links": { - "self": "../Guest/nana" - } - }, - "cardInfo.theme": { - "links": { - "self": null - } - }, - "cardInfo.cardThumbnail": { - "links": { - "self": null - } - } - }, - "meta": { - "adoptsFrom": { - "name": "PlaceCard", - "module": "../place-card" - } - } - } -} diff --git a/41e20f-wedding-table-seating-planner/TableCard/rose.json b/41e20f-wedding-table-seating-planner/TableCard/rose.json deleted file mode 100644 index 6a506c31..00000000 --- a/41e20f-wedding-table-seating-planner/TableCard/rose.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "data": { - "type": "card", - "attributes": { - "tableName": "Table 1", - "eventTitle": "Lucas & Amy", - "accent": "Rose", - "cardInfo": { - "name": null, - "notes": null, - "summary": null, - "cardThumbnailURL": null - } - }, - "relationships": { - "cardInfo.theme": { - "links": { - "self": null - } - }, - "cardInfo.cardThumbnail": { - "links": { - "self": null - } - } - }, - "meta": { - "adoptsFrom": { - "name": "TableCard", - "module": "../table-card" - } - } - } -} diff --git a/41e20f-wedding-table-seating-planner/card-views.gts b/41e20f-wedding-table-seating-planner/card-views.gts new file mode 100644 index 00000000..4979f00f --- /dev/null +++ b/41e20f-wedding-table-seating-planner/card-views.gts @@ -0,0 +1,156 @@ +import Component from '@glimmer/component'; + +// Shared visuals for the wedding stationery. Both the PlaceCard / TableCard +// card definitions AND the planner's print sheet render these, so the card and +// what gets printed stay identical (single source of truth). Sizing is driven +// by the container (container queries), so the same component looks right as a +// large isolated card, a small fitted tile, or a print-sheet cell. + +interface PlaceCardSignature { + Element: HTMLDivElement; + Args: { + eventTitle?: string | null; + guestName?: string | null; + tableName?: string | null; + message?: string | null; + }; +} + +export class PlaceCardView extends Component { + +} + +interface TableCardSignature { + Element: HTMLDivElement; + Args: { + eventTitle?: string | null; + tableName?: string | null; + accent?: string | null; + }; +} + +export class TableCardView extends Component { + +} diff --git a/41e20f-wedding-table-seating-planner/components/tsp.gts b/41e20f-wedding-table-seating-planner/components/tsp.gts index 24776792..0b82ef4d 100644 --- a/41e20f-wedding-table-seating-planner/components/tsp.gts +++ b/41e20f-wedding-table-seating-planner/components/tsp.gts @@ -32,6 +32,7 @@ import FixtureGlyph from './fixture-glyph'; import LayoutPreview from './layout-preview'; import ImageSourceField from '@cardstack/catalog/fields/image-source/image-source'; import SeatingPlanPopover from './seating-plan-popover'; +import { PlaceCardView, TableCardView } from '../card-views'; import PencilIcon from '@cardstack/boxel-icons/pencil'; import XIcon from '@cardstack/boxel-icons/x'; import LockIcon from '@cardstack/boxel-icons/lock'; @@ -42,6 +43,7 @@ import StarIcon from '@cardstack/boxel-icons/star'; import DownloadIcon from '@cardstack/boxel-icons/download'; import SearchIcon from '@cardstack/boxel-icons/search'; import CameraIcon from '@cardstack/boxel-icons/camera'; +import PrinterIcon from '@cardstack/boxel-icons/printer'; import ArrowsMoveIcon from '@cardstack/boxel-icons/arrows-move'; import TemplateIcon from '@cardstack/boxel-icons/template'; import RefreshIcon from '@cardstack/boxel-icons/refresh'; @@ -1066,7 +1068,7 @@ export class TableSeatingPlannerIsolated extends Component< addTableShape = (shape: string) => { this.addMenuOpen = false; let { x, y } = this.worldCenter(); - let n = this.tables.filter((t) => t.shape === shape).length + 1; + let n = this.tables.length + 1; let w = 150; let h = 150; let seatCount = 8; @@ -1090,7 +1092,7 @@ export class TableSeatingPlannerIsolated extends Component< } let label = TABLE_SHAPE_LABELS[shape] ?? 'Table'; let t = new Table({ - name: `${label} ${n}`, + name: label === 'Table' ? `Table ${n}` : `${label} Table ${n}`, shape, seatCount, seatingStyle: style, @@ -3281,6 +3283,28 @@ export class TableSeatingPlannerIsolated extends Component< this.savingSnapshot = false; } }; + @tracked printOnlyTable: Table | null = null; + get tablesToPrint(): Table[] { + return this.printOnlyTable ? [this.printOnlyTable] : this.tables; + } + private nextFrame(): Promise { + return new Promise((resolve) => + requestAnimationFrame(() => requestAnimationFrame(() => resolve())), + ); + } + printCards = async () => { + if (this.printOnlyTable) { + this.printOnlyTable = null; + await this.nextFrame(); + } + window.print(); + }; + printTable = async (t: Table) => { + this.printOnlyTable = t; + await this.nextFrame(); // let the print sheet re-render before printing + window.print(); + this.printOnlyTable = null; + }; private partyChildren(roster: Guest[]): Map { let inRoster = new Set(roster); let rootOf = (g: Guest): Guest => { @@ -4341,6 +4365,12 @@ export class TableSeatingPlannerIsolated extends Component< {{on 'click' this.saveSnapshot}} > {{if this.savingSnapshot 'Saving…' 'Save snapshot'}} +
+
{{this.selectedTable.seatedCount}} of @@ -5707,8 +5743,55 @@ export class TableSeatingPlannerIsolated extends Component< {{/if}} {{/if}} +
- - }; - - static fitted = class Fitted extends Component { - - }; -} diff --git a/41e20f-wedding-table-seating-planner/table-card.gts b/41e20f-wedding-table-seating-planner/table-card.gts deleted file mode 100644 index 3fbac7da..00000000 --- a/41e20f-wedding-table-seating-planner/table-card.gts +++ /dev/null @@ -1,149 +0,0 @@ -import { - CardDef, - Component, - field, - contains, -} from 'https://cardstack.com/base/card-api'; -import StringField from 'https://cardstack.com/base/string'; - -// A printable wedding table card / sign: a large table name or number with the -// event title, styled to match the planner's burgundy/gold theme. -export class TableCard extends CardDef { - static displayName = 'Table Card'; - - @field tableName = contains(StringField); - @field eventTitle = contains(StringField); - @field accent = contains(StringField); - - @field title = contains(StringField, { - computeVia: function (this: TableCard) { - return this.tableName?.trim() || 'Table Card'; - }, - }); - - static isolated = class Isolated extends Component { - - }; - - static fitted = class Fitted extends Component { - - }; -} From a6936559b6db47b91651c843120c3c9ffa6a9ae1 Mon Sep 17 00:00:00 2001 From: Lucas Law Date: Wed, 15 Jul 2026 21:37:54 +0800 Subject: [PATCH 05/13] Table Seating Planner: setup wizard, in-place guest edit, compact image-source editor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit linear: https://linear.app/cardstack/issue/CS-12106 ## summary: - Add a first-run setup wizard (setup-wizard.gts) shown only on a brand-new planner: a two-pane modal (left vertical stepper, right step content) covering event details, guests, and a "start from a template" step that previews saved layouts with the LayoutPreview SVG (or start blank). Full-bleed with a small margin; per-step validation gates Next; closes via βœ• / Start blank / applying a template. The planner owns template application (applyTemplateFromWizard) and shows the wizard via showWizard (decided once at construction so typing in it never dismisses it). - Make the Guest isolated view in-place editable (name input, category chips, VIP toggle) reusing the edit-format actions. - Redesign the shared ImageSourceField edit template (image-source-editor.gts) to be compact and modern with a segmented File/URL control and a themeable --image-source-accent (no hardcoded purple). --- .../components/tsp.gts | 45 ++ .../guest.gts | 118 +++- .../setup-wizard.gts | 619 ++++++++++++++++++ .../components/image-source-editor.gts | 496 +++++--------- 4 files changed, 936 insertions(+), 342 deletions(-) create mode 100644 41e20f-wedding-table-seating-planner/setup-wizard.gts diff --git a/41e20f-wedding-table-seating-planner/components/tsp.gts b/41e20f-wedding-table-seating-planner/components/tsp.gts index 0b82ef4d..b4eb445b 100644 --- a/41e20f-wedding-table-seating-planner/components/tsp.gts +++ b/41e20f-wedding-table-seating-planner/components/tsp.gts @@ -33,6 +33,7 @@ import LayoutPreview from './layout-preview'; import ImageSourceField from '@cardstack/catalog/fields/image-source/image-source'; import SeatingPlanPopover from './seating-plan-popover'; import { PlaceCardView, TableCardView } from '../card-views'; +import SetupWizard from '../setup-wizard'; import PencilIcon from '@cardstack/boxel-icons/pencil'; import XIcon from '@cardstack/boxel-icons/x'; import LockIcon from '@cardstack/boxel-icons/lock'; @@ -572,6 +573,33 @@ export class TableSeatingPlannerIsolated extends Component< get tableCount() { return this.tables.length; } + get hasCanvasContent(): boolean { + return ( + this.tables.length > 0 || this.fixtures.length > 0 || this.hasFloorPlan + ); + } + get hasEventInfo(): boolean { + return !!this.args.model?.eventTitle?.trim(); + } + + // --- First-run setup wizard (steps live in the SetupWizard component) --- + // Decided once at construction: the wizard opens only for a brand-new planner + // (empty canvas, no event info, no guests). Because it is NOT re-derived from + // those fields, typing the event name or adding a guest inside the wizard does + // not make it close itself. It closes only on Skip / βœ• / Create tables. + @tracked wizardDismissed = + this.hasCanvasContent || this.hasEventInfo || this.guests.length > 0; + get showWizard(): boolean { + return !this.wizardDismissed; + } + skipWizard = () => { + this.wizardDismissed = true; + }; + applyTemplateFromWizard = (index: number) => { + let tpl = this.templates[index]; + if (tpl) this.applyTemplate(tpl); + this.wizardDismissed = true; + }; get tableVMs(): TableVM[] { let rankMap = this.tableRank; return this.tables.map((t) => { @@ -5743,6 +5771,23 @@ export class TableSeatingPlannerIsolated extends Component< {{/if}} {{/if}} + {{#if this.showWizard}} + + {{/if}}