create-aiaui-module 0.1.1-TEST → 0.2.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 (52) hide show
  1. package/README.md +13 -12
  2. package/bin/create-aiaui-module.js +24 -32
  3. package/package.json +4 -3
  4. package/runtime/_runtime/host.py +7 -15
  5. package/runtime/_runtime/registry.py +4 -1
  6. package/templates/JobDashboard/CUSTOMIZATION.md +123 -0
  7. package/templates/{JobInformation → JobDashboard}/README.md +22 -9
  8. package/templates/JobDashboard/backend/api.py +144 -0
  9. package/templates/JobDashboard/backend/config.json +46 -0
  10. package/templates/JobDashboard/client/dist/modules.js +3513 -0
  11. package/templates/JobDashboard/client/src/contract.ts +146 -0
  12. package/templates/JobDashboard/client/src/customization.ts +30 -0
  13. package/templates/JobDashboard/client/src/defaults/DefaultPanel.vue +129 -0
  14. package/templates/JobDashboard/client/src/defaults/Icons.vue +49 -0
  15. package/templates/JobDashboard/client/src/defaults/InputCarriers.vue +18 -0
  16. package/templates/JobDashboard/client/src/defaults/JobBottom.vue +32 -0
  17. package/templates/JobDashboard/client/src/defaults/JobCard.vue +48 -0
  18. package/templates/JobDashboard/client/src/defaults/JobHeader.vue +41 -0
  19. package/templates/JobDashboard/client/src/defaults/JobMiddle.vue +41 -0
  20. package/templates/JobDashboard/client/src/defaults/JobName.vue +9 -0
  21. package/templates/JobDashboard/client/src/defaults/LotId.vue +14 -0
  22. package/templates/JobDashboard/client/src/defaults/OutputCarriers.vue +21 -0
  23. package/templates/JobDashboard/client/src/defaults/Recipe.vue +12 -0
  24. package/templates/JobDashboard/client/src/defaults/Status.vue +29 -0
  25. package/templates/JobDashboard/client/src/defaults/Timeline.vue +65 -0
  26. package/templates/JobDashboard/client/src/defaults/board-data.ts +36 -0
  27. package/templates/JobDashboard/client/src/defaults/config-defaults.ts +36 -0
  28. package/templates/JobDashboard/client/src/defaults/format.ts +51 -0
  29. package/templates/JobDashboard/client/src/defaults/slots.ts +26 -0
  30. package/templates/JobDashboard/client/src/defaults/styles.css +619 -0
  31. package/templates/{JobInformation/resources → JobDashboard/client}/src/index.ts +10 -18
  32. package/templates/JobInformation/CUSTOMIZATION.md +0 -173
  33. package/templates/JobInformation/api.py +0 -53
  34. package/templates/JobInformation/resources/dist/modules.js +0 -2919
  35. package/templates/JobInformation/resources/src/bus.ts +0 -38
  36. package/templates/JobInformation/resources/src/contract.ts +0 -74
  37. package/templates/JobInformation/resources/src/customization.ts +0 -25
  38. package/templates/JobInformation/resources/src/defaults/DefaultPanel.vue +0 -22
  39. package/templates/JobInformation/resources/src/defaults/Header.vue +0 -12
  40. package/templates/JobInformation/resources/src/defaults/InputCarriers.vue +0 -15
  41. package/templates/JobInformation/resources/src/defaults/OutputCarriers.vue +0 -14
  42. package/templates/JobInformation/resources/src/defaults/Recipe.vue +0 -18
  43. package/templates/JobInformation/resources/src/defaults/Timeline.vue +0 -22
  44. package/templates/JobInformation/resources/src/defaults/slots.ts +0 -16
  45. package/templates/JobInformation/resources/src/defaults/styles.css +0 -90
  46. /package/templates/{JobInformation/resources → JobDashboard/client}/build.mjs +0 -0
  47. /package/templates/{JobInformation/resources → JobDashboard/client}/dist/manifest.json +0 -0
  48. /package/templates/{JobInformation/resources → JobDashboard/client}/package-lock.json +0 -0
  49. /package/templates/{JobInformation/resources → JobDashboard/client}/package.json +0 -0
  50. /package/templates/{JobInformation/resources → JobDashboard/client}/src/vite-env.d.ts +0 -0
  51. /package/templates/{JobInformation/resources → JobDashboard/client}/vite.config.mjs +0 -0
  52. /package/templates/{JobInformation → JobDashboard}/contractVersion +0 -0
@@ -0,0 +1,146 @@
1
+ import type { Component } from "vue";
2
+
3
+ export type ModuleApi = {
4
+ call<T>(
5
+ method: string,
6
+ path: string,
7
+ options?: { query?: Record<string, unknown>; body?: unknown },
8
+ ): Promise<T>;
9
+ };
10
+
11
+ /* -------------------------------------------------------------------------- */
12
+ /* Slot names */
13
+ /* -------------------------------------------------------------------------- */
14
+
15
+ /**
16
+ * Every customizable slot of a job card. Leaves override a single piece;
17
+ * region slots override the whole region (leaf overrides then only apply
18
+ * inside the platform's default regions). Replaceable top to bottom:
19
+ *
20
+ * board the whole job card
21
+ * ├── headerRegion job name + lot id + status (slot.board also
22
+ * │ ├── jobName, lotId, status replaces the row)
23
+ * ├── middleRegion input carriers + timeline + output carriers
24
+ * │ └── inputCarriers, timeline, outputCarriers
25
+ * └── bottomRegion recipe area + footer (slot.board also
26
+ * └── recipe replaces footer)
27
+ */
28
+ export const SlotKey = {
29
+ board: "board",
30
+ headerRegion: "headerRegion",
31
+ jobName: "jobName",
32
+ lotId: "lotId",
33
+ status: "status",
34
+ middleRegion: "middleRegion",
35
+ inputCarriers: "inputCarriers",
36
+ timeline: "timeline",
37
+ outputCarriers: "outputCarriers",
38
+ bottomRegion: "bottomRegion",
39
+ recipe: "recipe",
40
+ } as const;
41
+
42
+ export type SlotKeyType = (typeof SlotKey)[keyof typeof SlotKey];
43
+
44
+ /** Any Vue component that renders one slot. Custom components receive `ModuleSlotProps`. */
45
+ export type SlotComponent = Component;
46
+
47
+ export type SlotMap = Record<string, SlotComponent>;
48
+
49
+ /* -------------------------------------------------------------------------- */
50
+ /* Default panel data contract */
51
+ /* -------------------------------------------------------------------------- */
52
+
53
+ /** One carrier entry rendered by the carrier slots. */
54
+ export type ModuleCarrier = { code: string };
55
+
56
+ /** One step of a job's timeline (rendered by the default `timeline` slot). */
57
+ export type ModuleTimelineStep = {
58
+ label: string;
59
+ state?: "done" | "active" | "pending";
60
+ /** ISO timestamp; durations between steps are computed client-side. */
61
+ at?: string | null;
62
+ };
63
+
64
+ /**
65
+ * One job entry returned by `GET /jobs` (the module's default data contract).
66
+ * Every item is rendered as one job card (the `board` slot).
67
+ */
68
+ export type ModuleJobRecord = {
69
+ id: string;
70
+ name: string;
71
+ lot?: string | null;
72
+ /** State name; matched against `config.states` for its color and icon. */
73
+ state?: string | null;
74
+ carriers?: { input?: ModuleCarrier[]; output?: ModuleCarrier[] };
75
+ timeline?: ModuleTimelineStep[];
76
+ recipe?: string | null;
77
+ };
78
+
79
+ /** Response of `GET /jobs` consumed by the default panel. */
80
+ export type ModuleJobPage = {
81
+ items: ModuleJobRecord[];
82
+ total: number;
83
+ page?: number;
84
+ pageSize?: number;
85
+ };
86
+
87
+ /** One state entry returned by `GET /config`. */
88
+ export type ModuleStateVisual = {
89
+ name: string;
90
+ color?: string | null;
91
+ icon?: string | null;
92
+ };
93
+
94
+ /**
95
+ * Response of `GET /config` (the module's configuration contract).
96
+ * Every field is optional; any missing field falls back to the template's
97
+ * built-in defaults (all parts visible, default state colors).
98
+ */
99
+ export type ModuleConfig = {
100
+ display?: {
101
+ lotId?: boolean;
102
+ recipe?: boolean;
103
+ inputCarriers?: boolean;
104
+ outputCarriers?: boolean;
105
+ timeline?: boolean;
106
+ };
107
+ /** State visuals for `<slot>status</slot>`; missing entries use the built-in token map. */
108
+ states?: ModuleStateVisual[];
109
+ };
110
+
111
+ /* -------------------------------------------------------------------------- */
112
+ /* Context & customization */
113
+ /* -------------------------------------------------------------------------- */
114
+
115
+ export type ModuleContext = {
116
+ module: string;
117
+ instanceId: string;
118
+ api: ModuleApi;
119
+ signal: AbortSignal;
120
+ place: (spec: {
121
+ kind: "route" | "slot";
122
+ path?: string;
123
+ slot?: string;
124
+ label?: string;
125
+ order?: number;
126
+ }) => void;
127
+ };
128
+
129
+ /**
130
+ * What the host platform actually passes into `mount`. The module owns everything
131
+ * outside this context and applies it to slots itself.
132
+ */
133
+ export type HostModuleContext = ModuleContext;
134
+
135
+ /**
136
+ * Props every slot component receives: the module context, the per-job record
137
+ * being rendered, and the merged module configuration.
138
+ */
139
+ export type ModuleSlotProps = {
140
+ context: ModuleContext;
141
+ job: ModuleJobRecord;
142
+ config: ModuleConfig;
143
+ };
144
+
145
+ /** A slot override map. Editing `resources/src/customization.ts` swaps these in. */
146
+ export type Customization = { slots: SlotMap };
@@ -0,0 +1,30 @@
1
+ import type { Customization } from "./contract";
2
+
3
+ /**
4
+ * Developer customization entry point.
5
+ *
6
+ * Default (no change): keeps the built-in job card layout.
7
+ *
8
+ * Replace one piece inside a card (layout stays platform-owned):
9
+ * import MyJobName from "./custom/MyJobName.vue";
10
+ * export const customization: Customization = {
11
+ * slots: { jobName: MyJobName },
12
+ * };
13
+ *
14
+ * Replace a whole region or the whole card:
15
+ * import MyMiddle from "./custom/MyMiddle.vue";
16
+ * export const customization: Customization = {
17
+ * slots: { middleRegion: MyMiddle }, // the three columns region
18
+ * };
19
+ * export const customization: Customization = {
20
+ * slots: { board: MyBoard }, // the entire single-job card
21
+ * };
22
+ *
23
+ * Every custom component receives `{ context, job, config }` as props
24
+ * (see `contract.ts` for the slot table and CUSTOMIZATION.md for the API/data
25
+ * contract). The platform, batch listing, `GET /jobs`/`GET /config` and card
26
+ * frame stay under AIAUI control regardless of which slot you replace.
27
+ */
28
+ export const customization: Customization = {
29
+ slots: {},
30
+ };
@@ -0,0 +1,129 @@
1
+ <script setup lang="ts">
2
+ import { computed, onMounted, ref } from "vue";
3
+ import type {
4
+ ModuleConfig,
5
+ ModuleContext,
6
+ ModuleJobPage,
7
+ SlotMap,
8
+ } from "../contract";
9
+ import { mergeConfig } from "./config-defaults";
10
+ import { DEMO_JOBS } from "./board-data";
11
+ import { defaultSlots } from "./slots";
12
+ import { customization } from "../customization";
13
+ import JobCard from "./JobCard.vue";
14
+ import Icons from "./Icons.vue";
15
+
16
+ const props = defineProps<{ context: ModuleContext; slotMap?: SlotMap }>();
17
+
18
+ const slotMap = computed(() =>
19
+ props.slotMap ?? { ...defaultSlots, ...(customization.slots ?? {}) },
20
+ );
21
+
22
+ const jobs = ref<ModuleJobPage["items"]>(DEMO_JOBS);
23
+ const config = ref<ModuleConfig>(mergeConfig(null));
24
+ const note = ref("");
25
+ const search = ref("");
26
+ const stateFilter = ref("");
27
+ const loading = ref(true);
28
+
29
+ async function loadConfig() {
30
+ const signal = props.context.signal;
31
+ try {
32
+ const raw = await props.context.api.call<ModuleConfig>("GET", "config");
33
+ if (signal.aborted) return;
34
+ config.value = mergeConfig(raw);
35
+ } catch {
36
+ if (signal.aborted) return;
37
+ // keep whatever config we have (defaults on first mount)
38
+ }
39
+ }
40
+
41
+ async function loadJobs() {
42
+ const signal = props.context.signal;
43
+ loading.value = true;
44
+ try {
45
+ const page = await props.context.api.call<ModuleJobPage>("GET", "jobs", {
46
+ query: { page: 1, pageSize: 10 },
47
+ });
48
+ if (signal.aborted) return;
49
+ jobs.value = page.items;
50
+ note.value = "";
51
+ } catch {
52
+ if (signal.aborted) return;
53
+ jobs.value = DEMO_JOBS;
54
+ note.value = "GET /jobs unavailable — showing demo data";
55
+ } finally {
56
+ if (!signal.aborted) loading.value = false;
57
+ }
58
+ }
59
+
60
+ async function refresh() {
61
+ await Promise.all([loadConfig(), loadJobs()]);
62
+ }
63
+
64
+ onMounted(refresh);
65
+
66
+ const states = computed(() =>
67
+ Array.from(new Set(jobs.value.map((job) => job.state ?? "—"))),
68
+ );
69
+
70
+ const visibleJobs = computed(() =>
71
+ jobs.value.filter((job) => {
72
+ const matchesName =
73
+ !search.value.trim() ||
74
+ job.name.toLowerCase().includes(search.value.trim().toLowerCase());
75
+ const matchesState =
76
+ !stateFilter.value || (job.state ?? "—") === stateFilter.value;
77
+ return matchesName && matchesState;
78
+ }),
79
+ );
80
+ </script>
81
+
82
+ <template>
83
+ <section class="legacy-jobs" aria-label="Job Dashboard">
84
+ <header class="legacy-toolbar">
85
+ <div class="legacy-filters">
86
+ <label class="legacy-search">
87
+ <Icons name="search" :size="14" />
88
+ <input
89
+ v-model="search"
90
+ placeholder="Search jobs"
91
+ type="text"
92
+ aria-label="Search jobs"
93
+ />
94
+ </label>
95
+ <select
96
+ v-model="stateFilter"
97
+ class="legacy-filter"
98
+ aria-label="Status filter"
99
+ >
100
+ <option value="">All statuses</option>
101
+ <option v-for="state in states" :key="state" :value="state">
102
+ {{ state }}
103
+ </option>
104
+ </select>
105
+ <button
106
+ class="legacy-refresh"
107
+ type="button"
108
+ title="Refresh"
109
+ :aria-busy="loading"
110
+ @click="refresh"
111
+ >
112
+ <Icons name="refresh" :size="14" :class="{ 'aia-spinning': loading }" />
113
+ </button>
114
+ </div>
115
+ </header>
116
+ <p v-if="note" class="legacy-job-note" role="note">{{ note }}</p>
117
+ <JobCard
118
+ v-for="job in visibleJobs"
119
+ :key="job.id"
120
+ :context="props.context"
121
+ :slotMap="slotMap"
122
+ :job="job"
123
+ :config="config"
124
+ />
125
+ <p v-if="visibleJobs.length === 0" class="legacy-job-empty">
126
+ No jobs match the current filters.
127
+ </p>
128
+ </section>
129
+ </template>
@@ -0,0 +1,49 @@
1
+ <script setup lang="ts">
2
+ /* Minimal inline icon set shared by the built-in slots (no external dependency). */
3
+
4
+ const props = defineProps<{
5
+ name:
6
+ | "clock"
7
+ | "list"
8
+ | "stop"
9
+ | "activity"
10
+ | "flag"
11
+ | "search"
12
+ | "refresh"
13
+ | "chevron-down"
14
+ | "package";
15
+ size?: number;
16
+ }>();
17
+
18
+ const paths: Record<string, string> = {
19
+ clock:
20
+ '<circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline>',
21
+ list: '<line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line>',
22
+ stop: '<circle cx="12" cy="12" r="10"></circle><rect x="9" y="9" width="6" height="6" rx="1"></rect>',
23
+ activity: '<path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>',
24
+ flag:
25
+ '<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"></path><line x1="4" y1="22" x2="4" y2="15"></line>',
26
+ search:
27
+ '<circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line>',
28
+ refresh:
29
+ '<polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>',
30
+ "chevron-down": '<polyline points="6 9 12 15 18 9"></polyline>',
31
+ package:
32
+ '<path d="m9 4c0-1.7 1.3-3 3-3s3 1.3 3 3v1h3.4c1.4 0 2.6 1.1 2.6 2.5v10c0 1.4-1.1 2.5-2.6 2.5H5.6C4.1 20 3 18.9 3 17.5v-10C3 6.1 4.1 5 5.6 5H9V4Z"></path><path d="M12 4v16"></path>',
33
+ };
34
+ </script>
35
+
36
+ <template>
37
+ <svg
38
+ :width="props.size ?? 16"
39
+ :height="props.size ?? 16"
40
+ viewBox="0 0 24 24"
41
+ fill="none"
42
+ stroke="currentColor"
43
+ stroke-width="2"
44
+ stroke-linecap="round"
45
+ stroke-linejoin="round"
46
+ aria-hidden="true"
47
+ v-html="paths[props.name]"
48
+ />
49
+ </template>
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ import type { ModuleSlotProps } from "../contract";
3
+
4
+ const props = defineProps<ModuleSlotProps>();
5
+ const carriers = props.job.carriers?.input ?? [];
6
+ </script>
7
+
8
+ <template>
9
+ <aside class="legacy-carriers legacy-carriers-in" :aria-label="`Input carriers for ${job.name}`">
10
+ <span class="legacy-carriers-label">INPUT CARRIER(S)</span>
11
+ <div class="legacy-carriers-list">
12
+ <span v-for="carrier in carriers" :key="carrier.code" class="legacy-carrier-chip">{{
13
+ carrier.code
14
+ }}</span>
15
+ <span v-if="carriers.length === 0" class="legacy-carrier-empty">—</span>
16
+ </div>
17
+ </aside>
18
+ </template>
@@ -0,0 +1,32 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+ import type {
4
+ ModuleConfig,
5
+ ModuleContext,
6
+ ModuleJobRecord,
7
+ SlotMap,
8
+ } from "../contract";
9
+ import Recipe from "./Recipe.vue";
10
+
11
+ const props = defineProps<{
12
+ context: ModuleContext;
13
+ job: ModuleJobRecord;
14
+ config: ModuleConfig;
15
+ slotMap?: SlotMap;
16
+ }>();
17
+
18
+ const leafProps = computed(() => ({
19
+ context: props.context,
20
+ job: props.job,
21
+ config: props.config,
22
+ }));
23
+
24
+ const showRecipe = computed(() => props.config.display?.recipe !== false);
25
+ const recipe = computed(() => (props.slotMap ?? {}).recipe ?? Recipe);
26
+ </script>
27
+
28
+ <template>
29
+ <div v-if="showRecipe" class="legacy-progress">
30
+ <component :is="recipe" v-bind="leafProps" />
31
+ </div>
32
+ </template>
@@ -0,0 +1,48 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+ import type { Component } from "vue";
4
+ import type {
5
+ ModuleConfig,
6
+ ModuleContext,
7
+ ModuleJobRecord,
8
+ SlotMap,
9
+ } from "../contract";
10
+ import JobHeader from "./JobHeader.vue";
11
+ import JobMiddle from "./JobMiddle.vue";
12
+ import JobBottom from "./JobBottom.vue";
13
+
14
+ /**
15
+ * Default `board` slot = one job card. Overriding `board` replaces the whole
16
+ * card (header, middle, bottom); leaf/region overrides then only apply when
17
+ * the platform's default board composes them.
18
+ */
19
+ const props = defineProps<{
20
+ context: ModuleContext;
21
+ job: ModuleJobRecord;
22
+ config: ModuleConfig;
23
+ slotMap?: SlotMap;
24
+ }>();
25
+
26
+ const board = computed(() => props.slotMap?.board ?? null);
27
+ const regionProps = computed(() => ({ context: props.context, job: props.job, config: props.config, slotMap: props.slotMap }));
28
+ const headerRegion = computed(() => props.slotMap?.headerRegion ?? JobHeader);
29
+ const middleRegion = computed(() => props.slotMap?.middleRegion ?? JobMiddle);
30
+ const bottomRegion = computed(() => props.slotMap?.bottomRegion ?? JobBottom);
31
+ </script>
32
+
33
+ <template>
34
+ <article class="legacy-card" :data-job-id="job.id">
35
+ <component
36
+ v-if="board"
37
+ :is="board as Component"
38
+ :context="context"
39
+ :job="job"
40
+ :config="config"
41
+ />
42
+ <template v-else>
43
+ <component :is="headerRegion" v-bind="regionProps" />
44
+ <component :is="middleRegion" v-bind="regionProps" />
45
+ <component :is="bottomRegion" v-bind="regionProps" />
46
+ </template>
47
+ </article>
48
+ </template>
@@ -0,0 +1,41 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+ import type {
4
+ ModuleConfig,
5
+ ModuleContext,
6
+ ModuleJobRecord,
7
+ SlotMap,
8
+ } from "../contract";
9
+ import JobName from "./JobName.vue";
10
+ import LotId from "./LotId.vue";
11
+ import Status from "./Status.vue";
12
+
13
+ const props = defineProps<{
14
+ context: ModuleContext;
15
+ job: ModuleJobRecord;
16
+ config: ModuleConfig;
17
+ slotMap?: SlotMap;
18
+ }>();
19
+
20
+ const leafProps = computed(() => ({
21
+ context: props.context,
22
+ job: props.job,
23
+ config: props.config,
24
+ }));
25
+
26
+ const jobName = computed(() => (props.slotMap ?? {}).jobName ?? JobName);
27
+ const lotId = computed(() =>
28
+ props.config.display?.lotId === false ? null : ((props.slotMap ?? {}).lotId ?? LotId),
29
+ );
30
+ const status = computed(() => (props.slotMap ?? {}).status ?? Status);
31
+ </script>
32
+
33
+ <template>
34
+ <div class="legacy-job-head">
35
+ <div class="legacy-job-main">
36
+ <component :is="jobName" v-bind="leafProps" />
37
+ <component v-if="lotId" :is="lotId" v-bind="leafProps" />
38
+ </div>
39
+ <component :is="status" v-bind="leafProps" />
40
+ </div>
41
+ </template>
@@ -0,0 +1,41 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+ import type {
4
+ ModuleConfig,
5
+ ModuleContext,
6
+ ModuleJobRecord,
7
+ SlotMap,
8
+ } from "../contract";
9
+ import InputCarriers from "./InputCarriers.vue";
10
+ import OutputCarriers from "./OutputCarriers.vue";
11
+ import Timeline from "./Timeline.vue";
12
+
13
+ const props = defineProps<{
14
+ context: ModuleContext;
15
+ job: ModuleJobRecord;
16
+ config: ModuleConfig;
17
+ slotMap?: SlotMap;
18
+ }>();
19
+
20
+ const leafProps = computed(() => ({
21
+ context: props.context,
22
+ job: props.job,
23
+ config: props.config,
24
+ }));
25
+
26
+ const showInput = computed(() => props.config.display?.inputCarriers !== false);
27
+ const showTimeline = computed(() => props.config.display?.timeline !== false);
28
+ const showOutput = computed(() => props.config.display?.outputCarriers !== false);
29
+
30
+ const inputCarriers = computed(() => (props.slotMap ?? {}).inputCarriers ?? InputCarriers);
31
+ const timeline = computed(() => (props.slotMap ?? {}).timeline ?? Timeline);
32
+ const outputCarriers = computed(() => (props.slotMap ?? {}).outputCarriers ?? OutputCarriers);
33
+ </script>
34
+
35
+ <template>
36
+ <div class="legacy-body">
37
+ <component v-if="showInput" :is="inputCarriers" v-bind="leafProps" />
38
+ <component v-if="showTimeline" :is="timeline" v-bind="leafProps" />
39
+ <component v-if="showOutput" :is="outputCarriers" v-bind="leafProps" />
40
+ </div>
41
+ </template>
@@ -0,0 +1,9 @@
1
+ <script setup lang="ts">
2
+ import type { ModuleSlotProps } from "../contract";
3
+
4
+ defineProps<ModuleSlotProps>();
5
+ </script>
6
+
7
+ <template>
8
+ <span class="legacy-job-name">{{ job.name }}</span>
9
+ </template>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ import type { ModuleSlotProps } from "../contract";
3
+ import Icons from "./Icons.vue";
4
+
5
+ defineProps<ModuleSlotProps>();
6
+ </script>
7
+
8
+ <template>
9
+ <span v-if="job.lot" class="legacy-lot" :title="`Lot ID · ${job.lot}`">
10
+ <Icons name="package" :size="12" />
11
+ <span class="legacy-lot-label">Lot ID</span>
12
+ <span class="legacy-lot-value">{{ job.lot }}</span>
13
+ </span>
14
+ </template>
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import type { ModuleSlotProps } from "../contract";
3
+
4
+ const props = defineProps<ModuleSlotProps>();
5
+ const carriers = props.job.carriers?.output ?? [];
6
+ </script>
7
+
8
+ <template>
9
+ <aside
10
+ class="legacy-carriers legacy-carriers-out"
11
+ :aria-label="`Output carriers for ${job.name}`"
12
+ >
13
+ <span class="legacy-carriers-label">OUTPUT CARRIER(S)</span>
14
+ <div class="legacy-carriers-list">
15
+ <span v-for="carrier in carriers" :key="carrier.code" class="legacy-carrier-chip">{{
16
+ carrier.code
17
+ }}</span>
18
+ <span v-if="carriers.length === 0" class="legacy-carrier-empty">—</span>
19
+ </div>
20
+ </aside>
21
+ </template>
@@ -0,0 +1,12 @@
1
+ <script setup lang="ts">
2
+ import type { ModuleSlotProps } from "../contract";
3
+
4
+ defineProps<ModuleSlotProps>();
5
+ </script>
6
+
7
+ <template>
8
+ <div class="legacy-recipe-row">
9
+ <span class="legacy-recipe-label">Recipe</span>
10
+ <span class="legacy-recipe">{{ job.recipe ?? "—" }}</span>
11
+ </div>
12
+ </template>
@@ -0,0 +1,29 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+ import type { ModuleSlotProps, ModuleStateVisual } from "../contract";
4
+ import { tokenStateIcon } from "./format";
5
+
6
+ const props = defineProps<ModuleSlotProps>();
7
+
8
+ const visual = computed<Partial<ModuleStateVisual>>(() => {
9
+ const hit = props.config.states?.find(
10
+ (s) => s.name.toLowerCase() === (props.job.state ?? "").toLowerCase(),
11
+ );
12
+ return hit ?? { color: null, icon: tokenStateIcon(props.job.state) };
13
+ });
14
+
15
+ const chipStyle = computed<Record<string, string> | undefined>(() => {
16
+ if (!visual.value.color) return undefined;
17
+ return {
18
+ color: visual.value.color,
19
+ borderColor: `color-mix(in srgb, ${visual.value.color} 22%, #ffffff)`,
20
+ background: `color-mix(in srgb, ${visual.value.color} 12%, #ffffff)`,
21
+ };
22
+ });
23
+ </script>
24
+
25
+ <template>
26
+ <span class="legacy-chip" :style="chipStyle" :data-state="job.state ?? ''">
27
+ {{ job.state ?? "—" }}
28
+ </span>
29
+ </template>