trekoon 0.2.7 → 0.2.8
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.
- package/README.md +60 -0
- package/docs/commands.md +100 -0
- package/docs/quickstart.md +74 -1
- package/package.json +2 -1
- package/src/board/assets/app.js +1498 -0
- package/src/board/assets/components/AppShell.js +17 -0
- package/src/board/assets/components/BoardTopbar.js +78 -0
- package/src/board/assets/components/ClampedText.js +31 -0
- package/src/board/assets/components/EpicRow.js +62 -0
- package/src/board/assets/components/EpicsOverview.js +43 -0
- package/src/board/assets/components/WorkspaceHeader.js +70 -0
- package/src/board/assets/components/assetMap.js +65 -0
- package/src/board/assets/index.html +76 -0
- package/src/board/assets/main.js +27 -0
- package/src/board/assets/manifest.json +12 -0
- package/src/board/assets/state/actions.js +334 -0
- package/src/board/assets/state/api.js +126 -0
- package/src/board/assets/state/store.js +172 -0
- package/src/board/assets/styles/board.css +1127 -0
- package/src/board/assets/utils/dom.js +308 -0
- package/src/board/install.ts +196 -0
- package/src/board/open-browser.ts +131 -0
- package/src/board/routes.ts +299 -0
- package/src/board/server.ts +184 -0
- package/src/board/snapshot.ts +277 -0
- package/src/board/types.ts +43 -0
- package/src/commands/board.ts +158 -0
- package/src/commands/help.ts +21 -0
- package/src/commands/init.ts +29 -0
- package/src/domain/mutation-service.ts +40 -0
- package/src/domain/tracker-domain.ts +11 -3
- package/src/runtime/cli-shell.ts +5 -0
- package/src/storage/path.ts +36 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function createBoardShellComponent() {
|
|
2
|
+
return {
|
|
3
|
+
template: `
|
|
4
|
+
<div class="board-shell-v2">
|
|
5
|
+
<section class="board-shell-v2__frame">
|
|
6
|
+
<div class="board-shell-v2__runtime-shell">
|
|
7
|
+
<div
|
|
8
|
+
id="board-runtime-root"
|
|
9
|
+
class="board-shell-v2__runtime"
|
|
10
|
+
data-board-runtime-root
|
|
11
|
+
></div>
|
|
12
|
+
</div>
|
|
13
|
+
</section>
|
|
14
|
+
</div>
|
|
15
|
+
`,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export function renderBoardTopbar(context) {
|
|
2
|
+
const {
|
|
3
|
+
buttonClasses,
|
|
4
|
+
currentNav,
|
|
5
|
+
escapeHtml,
|
|
6
|
+
neutralChipClasses,
|
|
7
|
+
renderIcon,
|
|
8
|
+
screen,
|
|
9
|
+
search,
|
|
10
|
+
searchScope,
|
|
11
|
+
selectedEpic,
|
|
12
|
+
theme,
|
|
13
|
+
} = context;
|
|
14
|
+
|
|
15
|
+
const navItems = [
|
|
16
|
+
{ id: "epics", label: "Epics", icon: "layers", action: 'data-nav="epics"' },
|
|
17
|
+
{ id: "board", label: "Board", icon: "view_kanban", action: 'data-nav-board="true"', disabled: !selectedEpic },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const navMarkup = navItems.map((item) => {
|
|
21
|
+
const isActive = currentNav === item.id;
|
|
22
|
+
const classes = [
|
|
23
|
+
"board-shell-topbar__nav-item",
|
|
24
|
+
isActive ? "is-active" : "",
|
|
25
|
+
].filter(Boolean).join(" ");
|
|
26
|
+
|
|
27
|
+
return `
|
|
28
|
+
<button type="button" class="${classes}" ${item.action} ${item.disabled ? "disabled" : ""} ${isActive ? 'aria-current="page"' : ""}>
|
|
29
|
+
${renderIcon(item.icon, "text-[16px]")} <span>${escapeHtml(item.label)}</span>
|
|
30
|
+
</button>
|
|
31
|
+
`;
|
|
32
|
+
}).join("");
|
|
33
|
+
|
|
34
|
+
const epicContext = selectedEpic
|
|
35
|
+
? escapeHtml(selectedEpic.title)
|
|
36
|
+
: escapeHtml(searchScope?.summary ?? "No epic selected");
|
|
37
|
+
|
|
38
|
+
return `
|
|
39
|
+
<header class="board-shell-topbar ${screen === "tasks" ? "board-shell-topbar--workspace" : ""}">
|
|
40
|
+
<div class="board-shell-topbar__identity">
|
|
41
|
+
<div class="board-shell-topbar__brand-mark" aria-hidden="true">
|
|
42
|
+
${renderIcon("rocket_launch", "text-[18px]")}
|
|
43
|
+
</div>
|
|
44
|
+
<div class="min-w-0">
|
|
45
|
+
<div class="board-shell-topbar__title-row">
|
|
46
|
+
<h1>Trekoon</h1>
|
|
47
|
+
<span class="${neutralChipClasses()}">Local repo</span>
|
|
48
|
+
</div>
|
|
49
|
+
<p class="board-shell-topbar__context">${epicContext}</p>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<nav class="board-shell-topbar__nav" aria-label="Board sections">
|
|
54
|
+
${navMarkup}
|
|
55
|
+
</nav>
|
|
56
|
+
|
|
57
|
+
<div class="board-shell-topbar__tools">
|
|
58
|
+
<label class="board-shell-topbar__search" aria-label="Search tasks and epics">
|
|
59
|
+
${renderIcon("search", "text-[16px] text-[var(--board-text-soft)]")}
|
|
60
|
+
<input id="board-search-input" type="search" placeholder="Search epics, tasks, subtasks" value="${escapeHtml(search)}" />
|
|
61
|
+
<span class="board-shell-topbar__search-kbd">/</span>
|
|
62
|
+
</label>
|
|
63
|
+
<div class="board-shell-topbar__actions">
|
|
64
|
+
<button type="button" class="${buttonClasses({ iconOnly: true })}" data-action="toggle-theme" aria-label="Toggle ${theme === "dark" ? "light" : "dark"} theme">
|
|
65
|
+
${renderIcon(theme === "dark" ? "light_mode" : "dark_mode", "text-[18px]")}
|
|
66
|
+
</button>
|
|
67
|
+
<details class="board-shell-topbar__meta">
|
|
68
|
+
<summary>${renderIcon("info", "text-[16px]")}</summary>
|
|
69
|
+
<div>
|
|
70
|
+
<p>Repo-backed board state and view preferences stay local to this workspace.</p>
|
|
71
|
+
<p class="mt-2 text-sm text-[var(--board-text-muted)]">Current scope: ${escapeHtml(searchScope?.summary ?? "Epic overview")}</p>
|
|
72
|
+
</div>
|
|
73
|
+
</details>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</header>
|
|
77
|
+
`;
|
|
78
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function renderClampedText(context) {
|
|
2
|
+
const {
|
|
3
|
+
buttonLabel = "Description",
|
|
4
|
+
className = "",
|
|
5
|
+
emptyText = "",
|
|
6
|
+
escapeHtml,
|
|
7
|
+
lineClamp = 2,
|
|
8
|
+
renderIcon,
|
|
9
|
+
text,
|
|
10
|
+
} = context;
|
|
11
|
+
|
|
12
|
+
const trimmed = text?.trim() ?? "";
|
|
13
|
+
if (!trimmed) {
|
|
14
|
+
return emptyText ? `<p class="board-clamped-text__empty ${escapeHtml(className)}">${escapeHtml(emptyText)}</p>` : "";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return `
|
|
18
|
+
<details class="board-clamped-text" data-clamped-text>
|
|
19
|
+
<summary class="board-clamped-text__summary">
|
|
20
|
+
<span class="board-clamped-text__preview board-clamped-text__preview--${lineClamp} ${escapeHtml(className)}">${escapeHtml(trimmed)}</span>
|
|
21
|
+
<span class="board-clamped-text__toggle" aria-label="Toggle ${escapeHtml(buttonLabel)}">
|
|
22
|
+
<span class="board-clamped-text__toggle-more">Show more ${renderIcon("expand_more", "text-[16px]")}</span>
|
|
23
|
+
<span class="board-clamped-text__toggle-less">Collapse ${renderIcon("expand_less", "text-[16px]")}</span>
|
|
24
|
+
</span>
|
|
25
|
+
</summary>
|
|
26
|
+
<div class="board-clamped-text__body ${escapeHtml(className)}">
|
|
27
|
+
${escapeHtml(trimmed).replaceAll("\n", "<br />")}
|
|
28
|
+
</div>
|
|
29
|
+
</details>
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export function renderEpicRow(context) {
|
|
2
|
+
const {
|
|
3
|
+
epic,
|
|
4
|
+
escapeHtml,
|
|
5
|
+
formatDate,
|
|
6
|
+
neutralChipClasses,
|
|
7
|
+
renderClampedText,
|
|
8
|
+
renderIcon,
|
|
9
|
+
renderStatusBadge,
|
|
10
|
+
selected,
|
|
11
|
+
} = context;
|
|
12
|
+
|
|
13
|
+
const totalTasks = Array.isArray(epic.taskIds) ? epic.taskIds.length : 0;
|
|
14
|
+
const counts = epic.counts || { blocked: 0, done: 0, in_progress: 0 };
|
|
15
|
+
const statusLabel = String(epic.status ?? "todo").replace(/_/g, " ");
|
|
16
|
+
const openLabel = `Open epic ${epic.title}`;
|
|
17
|
+
const descriptionMarkup = epic.description
|
|
18
|
+
? renderClampedText({
|
|
19
|
+
buttonLabel: `epic ${epic.title} description`,
|
|
20
|
+
className: "board-epic-row__description text-sm text-[var(--board-text-muted)]",
|
|
21
|
+
escapeHtml,
|
|
22
|
+
lineClamp: 3,
|
|
23
|
+
renderIcon,
|
|
24
|
+
text: epic.description,
|
|
25
|
+
})
|
|
26
|
+
: "";
|
|
27
|
+
|
|
28
|
+
return `
|
|
29
|
+
<button
|
|
30
|
+
type="button"
|
|
31
|
+
class="board-epic-row ${selected ? "board-epic-row--selected" : ""}"
|
|
32
|
+
aria-current="${selected}"
|
|
33
|
+
aria-label="${escapeHtml(`${openLabel}. ${totalTasks} tasks. Status ${statusLabel}.`)}"
|
|
34
|
+
data-open-epic="${escapeHtml(epic.id)}"
|
|
35
|
+
>
|
|
36
|
+
<span class="board-epic-row__summary">
|
|
37
|
+
<span class="board-epic-row__title-row">
|
|
38
|
+
<span class="${neutralChipClasses()}">${escapeHtml(epic.id)}</span>
|
|
39
|
+
<strong class="board-epic-row__title">${escapeHtml(epic.title)}</strong>
|
|
40
|
+
</span>
|
|
41
|
+
${descriptionMarkup}
|
|
42
|
+
</span>
|
|
43
|
+
<span class="board-epic-row__status">${renderStatusBadge(epic.status ?? "todo")}</span>
|
|
44
|
+
<span class="board-epic-row__counts" aria-label="Epic progress counts">
|
|
45
|
+
<span class="${neutralChipClasses()}">${totalTasks} task${totalTasks === 1 ? "" : "s"}</span>
|
|
46
|
+
<span class="${neutralChipClasses()}">${counts.in_progress ?? 0} doing</span>
|
|
47
|
+
<span class="${neutralChipClasses()}">${counts.done ?? 0} done</span>
|
|
48
|
+
${(counts.blocked ?? 0) > 0 ? `<span class="${neutralChipClasses()}">${counts.blocked} blocked</span>` : ""}
|
|
49
|
+
</span>
|
|
50
|
+
<span class="board-epic-row__updated">
|
|
51
|
+
<span class="board-epic-row__label">Updated</span>
|
|
52
|
+
<span>${escapeHtml(formatDate(epic.updatedAt))}</span>
|
|
53
|
+
</span>
|
|
54
|
+
<span class="board-epic-row__action-wrap" aria-hidden="true">
|
|
55
|
+
<span class="board-epic-row__action">
|
|
56
|
+
<span>View board</span>
|
|
57
|
+
${renderIcon("chevron_right", "text-[16px]")}
|
|
58
|
+
</span>
|
|
59
|
+
</span>
|
|
60
|
+
</button>
|
|
61
|
+
`;
|
|
62
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function renderEpicsOverview(context) {
|
|
2
|
+
const {
|
|
3
|
+
panelClasses,
|
|
4
|
+
renderEmptyState,
|
|
5
|
+
renderEpicRow,
|
|
6
|
+
sectionLabelClasses,
|
|
7
|
+
store,
|
|
8
|
+
visibleEpics,
|
|
9
|
+
} = context;
|
|
10
|
+
|
|
11
|
+
return `
|
|
12
|
+
<div class="board-root board-root--epics">
|
|
13
|
+
<section class="board-overview ${panelClasses("board-overview--dense p-4 sm:p-5")}" aria-label="Epics overview">
|
|
14
|
+
<header class="board-section-head board-overview__header">
|
|
15
|
+
<div>
|
|
16
|
+
<span class="${sectionLabelClasses()}">Epics overview</span>
|
|
17
|
+
<h2 class="board-overview__title">Open an initiative and drive the next move</h2>
|
|
18
|
+
<p class="board-overview__summary">Each card is the entry point, so status, task counts, and freshness stay visible at a glance.</p>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="board-legend board-overview__legend">
|
|
21
|
+
<span class="board-chip board-chip--neutral">${visibleEpics.length} visible epic${visibleEpics.length === 1 ? "" : "s"}</span>
|
|
22
|
+
<span class="board-chip board-chip--neutral">${store.snapshot.tasks.length} total tasks</span>
|
|
23
|
+
${store.isMutating ? '<span class="board-chip board-chip--neutral">Saving…</span>' : ""}
|
|
24
|
+
</div>
|
|
25
|
+
</header>
|
|
26
|
+
<div class="board-table board-table--epics">
|
|
27
|
+
<div class="board-table__header board-table__header--epics hidden md:grid">
|
|
28
|
+
<span>Epic</span>
|
|
29
|
+
<span>Status</span>
|
|
30
|
+
<span>Counts</span>
|
|
31
|
+
<span>Updated</span>
|
|
32
|
+
<span>Action</span>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="board-table__rows board-table__rows--epics">
|
|
35
|
+
${visibleEpics.length === 0
|
|
36
|
+
? renderEmptyState("No matching epics", "Try a different search or publish more work to the board.", "/")
|
|
37
|
+
: visibleEpics.map((epic) => renderEpicRow(epic)).join("")}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</section>
|
|
41
|
+
</div>
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export function renderWorkspaceHeader(context) {
|
|
2
|
+
const {
|
|
3
|
+
escapeHtml,
|
|
4
|
+
fieldClasses,
|
|
5
|
+
isCompactViewport,
|
|
6
|
+
neutralChipClasses,
|
|
7
|
+
primarySurfaceLabel,
|
|
8
|
+
renderEpicCountSummary,
|
|
9
|
+
renderIcon,
|
|
10
|
+
renderStatusBadge,
|
|
11
|
+
sectionLabelClasses,
|
|
12
|
+
searchScope,
|
|
13
|
+
selectedEpic,
|
|
14
|
+
snapshotEpics,
|
|
15
|
+
store,
|
|
16
|
+
visibleTasks,
|
|
17
|
+
} = context;
|
|
18
|
+
|
|
19
|
+
const description = selectedEpic.description?.trim() || "No epic description yet.";
|
|
20
|
+
|
|
21
|
+
return `
|
|
22
|
+
<header class="board-section-head board-section-head--workspace board-workspace-header">
|
|
23
|
+
<div class="board-workspace-header__intro">
|
|
24
|
+
<div class="board-workspace-header__title-block">
|
|
25
|
+
<span class="${sectionLabelClasses()}">${escapeHtml(searchScope?.summary ?? "Selected epic")}</span>
|
|
26
|
+
<div class="board-workspace-header__title-row">
|
|
27
|
+
<h2>${escapeHtml(selectedEpic.title)}</h2>
|
|
28
|
+
${renderStatusBadge(selectedEpic.status)}
|
|
29
|
+
${isCompactViewport ? `<span class="${neutralChipClasses()}">Primary surface · ${escapeHtml(primarySurfaceLabel)}</span>` : ""}
|
|
30
|
+
</div>
|
|
31
|
+
<div class="mt-3 flex flex-wrap gap-2">
|
|
32
|
+
<span class="${neutralChipClasses()}">${escapeHtml(searchScope?.detail ?? "")}</span>
|
|
33
|
+
<span class="${neutralChipClasses()}">${visibleTasks.length} visible task${visibleTasks.length === 1 ? "" : "s"}</span>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
<details class="board-workspace-header__details">
|
|
37
|
+
<summary>
|
|
38
|
+
${renderIcon("subject", "text-[18px]")}
|
|
39
|
+
<span>${searchScope?.kind === "epic_search" ? "Epic scope" : "Epic notes"}</span>
|
|
40
|
+
</summary>
|
|
41
|
+
<p>${escapeHtml(description)}</p>
|
|
42
|
+
</details>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="board-workspace__toolbar board-workspace-header__toolbar">
|
|
46
|
+
<label class="board-select grid gap-2 xl:min-w-[240px]" aria-label="Choose epic">
|
|
47
|
+
<span class="${sectionLabelClasses()}">Epic</span>
|
|
48
|
+
<select class="${fieldClasses()}" id="board-epic-select">
|
|
49
|
+
${snapshotEpics.map((epic) => `
|
|
50
|
+
<option value="${escapeHtml(epic.id)}" ${store.selectedEpicId === epic.id ? "selected" : ""}>
|
|
51
|
+
${escapeHtml(epic.title)}
|
|
52
|
+
</option>
|
|
53
|
+
`).join("")}
|
|
54
|
+
</select>
|
|
55
|
+
</label>
|
|
56
|
+
<div class="board-workspace-header__controls">
|
|
57
|
+
<div class="board-tabs inline-flex rounded-2xl border border-[var(--board-border)] bg-white/[0.03] p-1" role="tablist" aria-label="Board views">
|
|
58
|
+
${store.viewModes.map((view) => `<button class="${view.classes}" type="button" role="tab" aria-selected="${view.active}" data-view="${view.id}">${renderIcon(view.icon, "text-[18px]")} ${view.label}</button>`).join("")}
|
|
59
|
+
</div>
|
|
60
|
+
<div class="board-legend board-workspace-header__legend">
|
|
61
|
+
${renderEpicCountSummary(selectedEpic)}
|
|
62
|
+
<span class="${neutralChipClasses()}">${escapeHtml(searchScope?.summary ?? "Current scope")}</span>
|
|
63
|
+
${store.view === "kanban" ? `<span class="${neutralChipClasses()}">Drag to move</span>` : ""}
|
|
64
|
+
${store.isMutating ? `<span class="${neutralChipClasses()}">Saving…</span>` : ""}
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</header>
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export const BOARD_SHARED_TOKENS = {
|
|
2
|
+
theme: "src/board/assets/styles/board.css",
|
|
3
|
+
shell: "src/board/assets/components/AppShell.js",
|
|
4
|
+
bootstrap: "src/board/assets/main.js",
|
|
5
|
+
legacyRuntime: "src/board/assets/app.js",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const BOARD_ASSET_MAP = {
|
|
9
|
+
shell: {
|
|
10
|
+
entry: "src/board/assets/main.js",
|
|
11
|
+
files: [
|
|
12
|
+
"src/board/assets/index.html",
|
|
13
|
+
"src/board/assets/main.js",
|
|
14
|
+
"src/board/assets/styles/board.css",
|
|
15
|
+
"src/board/assets/components/AppShell.js",
|
|
16
|
+
"src/board/assets/components/assetMap.js",
|
|
17
|
+
],
|
|
18
|
+
owner: "board-runtime",
|
|
19
|
+
description: "Zero-build CDN entry shell and runtime handoff.",
|
|
20
|
+
},
|
|
21
|
+
overview: {
|
|
22
|
+
files: ["src/board/assets/components/EpicsOverview.js", "src/board/assets/components/EpicRow.js", "src/board/assets/components/ClampedText.js"],
|
|
23
|
+
owner: "overview-lane",
|
|
24
|
+
description: "Epic list density, overview rows, and long-copy disclosure.",
|
|
25
|
+
},
|
|
26
|
+
workspace: {
|
|
27
|
+
files: [
|
|
28
|
+
"src/board/assets/components/TaskWorkspace.js",
|
|
29
|
+
"src/board/assets/components/KanbanBoard.js",
|
|
30
|
+
"src/board/assets/components/KanbanColumn.js",
|
|
31
|
+
"src/board/assets/components/TaskCard.js",
|
|
32
|
+
"src/board/assets/components/TaskList.js",
|
|
33
|
+
"src/board/assets/components/TaskListRow.js",
|
|
34
|
+
],
|
|
35
|
+
owner: "workspace-lane",
|
|
36
|
+
description: "Task browsing surfaces and drag-friendly work views.",
|
|
37
|
+
},
|
|
38
|
+
detail: {
|
|
39
|
+
files: [
|
|
40
|
+
"src/board/assets/components/TaskInspector.js",
|
|
41
|
+
"src/board/assets/components/TaskModal.js",
|
|
42
|
+
"src/board/assets/components/SubtaskModal.js",
|
|
43
|
+
"src/board/assets/components/DependencyList.js",
|
|
44
|
+
"src/board/assets/components/SubtaskList.js",
|
|
45
|
+
],
|
|
46
|
+
owner: "detail-lane",
|
|
47
|
+
description: "Task and subtask detail surfaces, forms, and disclosures.",
|
|
48
|
+
},
|
|
49
|
+
state: {
|
|
50
|
+
files: [
|
|
51
|
+
"src/board/assets/state/store.js",
|
|
52
|
+
"src/board/assets/state/actions.js",
|
|
53
|
+
"src/board/assets/state/api.js",
|
|
54
|
+
],
|
|
55
|
+
owner: "state-lane",
|
|
56
|
+
description: "Snapshot normalization, persistence, mutations, and API wiring.",
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export function listBoardAssetFamilies() {
|
|
61
|
+
return Object.entries(BOARD_ASSET_MAP).map(([family, value]) => ({
|
|
62
|
+
family,
|
|
63
|
+
...value,
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" data-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta
|
|
7
|
+
name="description"
|
|
8
|
+
content="Trekoon board — local-first workspace for browsing epics, tasks, and flow states."
|
|
9
|
+
/>
|
|
10
|
+
<title>Trekoon Board</title>
|
|
11
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
12
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
13
|
+
<link
|
|
14
|
+
rel="stylesheet"
|
|
15
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Material+Symbols+Rounded:FILL@0&display=swap"
|
|
16
|
+
/>
|
|
17
|
+
<script>
|
|
18
|
+
tailwind = window.tailwind || {};
|
|
19
|
+
tailwind.config = {
|
|
20
|
+
theme: {
|
|
21
|
+
extend: {
|
|
22
|
+
fontFamily: {
|
|
23
|
+
sans: ["Inter", "ui-sans-serif", "system-ui", "sans-serif"],
|
|
24
|
+
},
|
|
25
|
+
boxShadow: {
|
|
26
|
+
panel: "var(--board-shadow)",
|
|
27
|
+
lift: "0 18px 52px rgba(0, 0, 0, 0.34)",
|
|
28
|
+
focus: "0 0 0 1px var(--board-border-strong), 0 16px 40px rgba(88, 28, 135, 0.2)",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
</script>
|
|
34
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
35
|
+
<link rel="stylesheet" href="./styles/board.css" />
|
|
36
|
+
<style>
|
|
37
|
+
.material-symbols-rounded {
|
|
38
|
+
font-family: "Material Symbols Rounded";
|
|
39
|
+
font-weight: 400;
|
|
40
|
+
font-style: normal;
|
|
41
|
+
font-size: 20px;
|
|
42
|
+
line-height: 1;
|
|
43
|
+
letter-spacing: normal;
|
|
44
|
+
text-transform: none;
|
|
45
|
+
display: inline-block;
|
|
46
|
+
white-space: nowrap;
|
|
47
|
+
direction: ltr;
|
|
48
|
+
font-variation-settings:
|
|
49
|
+
"FILL" 0,
|
|
50
|
+
"wght" 400,
|
|
51
|
+
"GRAD" 0,
|
|
52
|
+
"opsz" 20;
|
|
53
|
+
}
|
|
54
|
+
</style>
|
|
55
|
+
</head>
|
|
56
|
+
<body>
|
|
57
|
+
<main id="app" aria-live="polite" class="min-h-screen">
|
|
58
|
+
<section class="mx-auto flex min-h-screen max-w-4xl items-center justify-center px-4 py-10 sm:px-6">
|
|
59
|
+
<div class="w-full rounded-[32px] border border-[var(--board-border)] bg-[var(--board-surface)]/95 p-8 text-center shadow-panel backdrop-blur-xl">
|
|
60
|
+
<div class="mx-auto mb-5 flex h-14 w-14 items-center justify-center rounded-2xl bg-[var(--board-accent-soft)] text-[var(--board-accent)] ring-1 ring-[var(--board-border-strong)]">
|
|
61
|
+
<span class="material-symbols-rounded" aria-hidden="true">rocket_launch</span>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="mb-3 inline-flex items-center gap-2 rounded-full border border-[var(--board-border)] bg-white/5 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-[var(--board-text-soft)]">
|
|
64
|
+
Trekoon board
|
|
65
|
+
</div>
|
|
66
|
+
<h1 class="text-3xl font-semibold tracking-tight text-[var(--board-text)]">Preparing your local workspace…</h1>
|
|
67
|
+
<p class="mx-auto mt-3 max-w-2xl text-sm leading-6 text-[var(--board-text-muted)] sm:text-base">
|
|
68
|
+
Loading epics, tasks, and saved board context from your repo-shared storage.
|
|
69
|
+
</p>
|
|
70
|
+
</div>
|
|
71
|
+
</section>
|
|
72
|
+
</main>
|
|
73
|
+
|
|
74
|
+
<script type="module" src="./main.js"></script>
|
|
75
|
+
</body>
|
|
76
|
+
</html>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createApp, nextTick } from "https://unpkg.com/vue@3.5.13/dist/vue.esm-browser.js";
|
|
2
|
+
|
|
3
|
+
import { createBoardShellComponent } from "./components/AppShell.js";
|
|
4
|
+
|
|
5
|
+
window.__TREKOON_BOARD_BOOTSTRAP__ = "main";
|
|
6
|
+
|
|
7
|
+
const appRoot = document.querySelector("#app");
|
|
8
|
+
|
|
9
|
+
if (!(appRoot instanceof HTMLElement)) {
|
|
10
|
+
throw new Error("Board shell could not find the app root.");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const shellApp = createApp(createBoardShellComponent());
|
|
14
|
+
shellApp.mount(appRoot);
|
|
15
|
+
await nextTick();
|
|
16
|
+
|
|
17
|
+
const runtimeRoot = appRoot.querySelector("[data-board-runtime-root]");
|
|
18
|
+
|
|
19
|
+
if (!(runtimeRoot instanceof HTMLElement)) {
|
|
20
|
+
throw new Error("Board shell could not find the runtime mount root.");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { bootLegacyBoard } = await import("./app.js");
|
|
24
|
+
|
|
25
|
+
await bootLegacyBoard({
|
|
26
|
+
mountElement: runtimeRoot,
|
|
27
|
+
});
|