mrvn-cli 0.4.8 → 0.4.10
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/dist/index.js +196 -26
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +195 -25
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +196 -26
- package/dist/marvin.js.map +1 -1
- package/package.json +1 -1
package/dist/marvin-serve.js
CHANGED
|
@@ -14401,7 +14401,8 @@ function createActionTools(store) {
|
|
|
14401
14401
|
priority: external_exports.string().optional().describe("Priority (high, medium, low)"),
|
|
14402
14402
|
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
|
|
14403
14403
|
dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
|
|
14404
|
-
sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (e.g. ['SP-001']). Adds sprint:SP-xxx tags.")
|
|
14404
|
+
sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (e.g. ['SP-001']). Adds sprint:SP-xxx tags."),
|
|
14405
|
+
workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Adds a stream:<value> tag.")
|
|
14405
14406
|
},
|
|
14406
14407
|
async (args) => {
|
|
14407
14408
|
const tags = [...args.tags ?? []];
|
|
@@ -14411,6 +14412,9 @@ function createActionTools(store) {
|
|
|
14411
14412
|
if (!tags.includes(tag)) tags.push(tag);
|
|
14412
14413
|
}
|
|
14413
14414
|
}
|
|
14415
|
+
if (args.workStream) {
|
|
14416
|
+
tags.push(`stream:${args.workStream}`);
|
|
14417
|
+
}
|
|
14414
14418
|
const doc = store.create(
|
|
14415
14419
|
"action",
|
|
14416
14420
|
{
|
|
@@ -14448,10 +14452,11 @@ function createActionTools(store) {
|
|
|
14448
14452
|
priority: external_exports.string().optional().describe("New priority"),
|
|
14449
14453
|
dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
|
|
14450
14454
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace all tags. When provided with sprints, sprint tags are merged into this array."),
|
|
14451
|
-
sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (replaces existing sprint tags). E.g. ['SP-001'].")
|
|
14455
|
+
sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (replaces existing sprint tags). E.g. ['SP-001']."),
|
|
14456
|
+
workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Replaces existing stream:<value> tag.")
|
|
14452
14457
|
},
|
|
14453
14458
|
async (args) => {
|
|
14454
|
-
const { id, content, sprints, tags, ...updates } = args;
|
|
14459
|
+
const { id, content, sprints, tags, workStream, ...updates } = args;
|
|
14455
14460
|
if (tags !== void 0) {
|
|
14456
14461
|
const merged = [...tags];
|
|
14457
14462
|
if (sprints) {
|
|
@@ -14460,8 +14465,14 @@ function createActionTools(store) {
|
|
|
14460
14465
|
if (!merged.includes(tag)) merged.push(tag);
|
|
14461
14466
|
}
|
|
14462
14467
|
}
|
|
14463
|
-
|
|
14464
|
-
|
|
14468
|
+
if (workStream !== void 0) {
|
|
14469
|
+
const filtered = merged.filter((t) => !t.startsWith("stream:"));
|
|
14470
|
+
filtered.push(`stream:${workStream}`);
|
|
14471
|
+
updates.tags = filtered;
|
|
14472
|
+
} else {
|
|
14473
|
+
updates.tags = merged;
|
|
14474
|
+
}
|
|
14475
|
+
} else if (sprints !== void 0 || workStream !== void 0) {
|
|
14465
14476
|
const existing = store.get(id);
|
|
14466
14477
|
if (!existing) {
|
|
14467
14478
|
return {
|
|
@@ -14469,10 +14480,16 @@ function createActionTools(store) {
|
|
|
14469
14480
|
isError: true
|
|
14470
14481
|
};
|
|
14471
14482
|
}
|
|
14472
|
-
|
|
14473
|
-
|
|
14474
|
-
|
|
14475
|
-
|
|
14483
|
+
let existingTags = existing.frontmatter.tags ?? [];
|
|
14484
|
+
if (sprints !== void 0) {
|
|
14485
|
+
existingTags = existingTags.filter((t) => !t.startsWith("sprint:"));
|
|
14486
|
+
existingTags.push(...sprints.map((s) => `sprint:${s}`));
|
|
14487
|
+
}
|
|
14488
|
+
if (workStream !== void 0) {
|
|
14489
|
+
existingTags = existingTags.filter((t) => !t.startsWith("stream:"));
|
|
14490
|
+
existingTags.push(`stream:${workStream}`);
|
|
14491
|
+
}
|
|
14492
|
+
updates.tags = existingTags;
|
|
14476
14493
|
}
|
|
14477
14494
|
const doc = store.update(id, updates, content);
|
|
14478
14495
|
return {
|
|
@@ -14637,14 +14654,19 @@ function createDocumentTools(store) {
|
|
|
14637
14654
|
{
|
|
14638
14655
|
type: external_exports.string().optional().describe(`Filter by document type (registered types: ${store.registeredTypes.join(", ")})`),
|
|
14639
14656
|
status: external_exports.string().optional().describe("Filter by status"),
|
|
14640
|
-
tag: external_exports.string().optional().describe("Filter by tag")
|
|
14657
|
+
tag: external_exports.string().optional().describe("Filter by tag"),
|
|
14658
|
+
workStream: external_exports.string().optional().describe("Filter by work stream name (matches stream:<value> tag)")
|
|
14641
14659
|
},
|
|
14642
14660
|
async (args) => {
|
|
14643
|
-
|
|
14661
|
+
let docs = store.list({
|
|
14644
14662
|
type: args.type,
|
|
14645
14663
|
status: args.status,
|
|
14646
14664
|
tag: args.tag
|
|
14647
14665
|
});
|
|
14666
|
+
if (args.workStream) {
|
|
14667
|
+
const streamTag = `stream:${args.workStream}`;
|
|
14668
|
+
docs = docs.filter((d) => d.frontmatter.tags?.includes(streamTag));
|
|
14669
|
+
}
|
|
14648
14670
|
const summary = docs.map((d) => ({
|
|
14649
14671
|
id: d.frontmatter.id,
|
|
14650
14672
|
title: d.frontmatter.title,
|
|
@@ -15558,7 +15580,7 @@ function collectSprintSummaryData(store, sprintId) {
|
|
|
15558
15580
|
});
|
|
15559
15581
|
const sprintTag = `sprint:${fm.id}`;
|
|
15560
15582
|
const workItemDocs = allDocs.filter(
|
|
15561
|
-
(d) => d.frontmatter.type !== "sprint" && d.frontmatter.type !== "epic" && d.frontmatter.tags?.includes(sprintTag)
|
|
15583
|
+
(d) => d.frontmatter.type !== "sprint" && d.frontmatter.type !== "epic" && d.frontmatter.type !== "meeting" && d.frontmatter.tags?.includes(sprintTag)
|
|
15562
15584
|
);
|
|
15563
15585
|
const primaryDocs = workItemDocs.filter((d) => d.frontmatter.type !== "contribution");
|
|
15564
15586
|
const byStatus = {};
|
|
@@ -15581,11 +15603,13 @@ function collectSprintSummaryData(store, sprintId) {
|
|
|
15581
15603
|
const sprintItemIds = new Set(workItemDocs.map((d) => d.frontmatter.id));
|
|
15582
15604
|
for (const doc of workItemDocs) {
|
|
15583
15605
|
const about = doc.frontmatter.aboutArtifact;
|
|
15606
|
+
const streamTag = (doc.frontmatter.tags ?? []).find((t) => t.startsWith("stream:"));
|
|
15584
15607
|
const item = {
|
|
15585
15608
|
id: doc.frontmatter.id,
|
|
15586
15609
|
title: doc.frontmatter.title,
|
|
15587
15610
|
type: doc.frontmatter.type,
|
|
15588
15611
|
status: doc.frontmatter.status,
|
|
15612
|
+
workStream: streamTag ? streamTag.slice(7) : void 0,
|
|
15589
15613
|
aboutArtifact: about
|
|
15590
15614
|
};
|
|
15591
15615
|
allItemsById.set(item.id, item);
|
|
@@ -16605,7 +16629,8 @@ function createContributionTools(store) {
|
|
|
16605
16629
|
contributionType: external_exports.string().describe("Type of contribution (e.g. 'action-result', 'risk-finding')"),
|
|
16606
16630
|
aboutArtifact: external_exports.string().describe("Artifact ID this contribution relates to (e.g. 'A-001', 'T-003')"),
|
|
16607
16631
|
status: external_exports.string().optional().describe("Status (default: 'done')"),
|
|
16608
|
-
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization")
|
|
16632
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
|
|
16633
|
+
workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Adds a stream:<value> tag.")
|
|
16609
16634
|
},
|
|
16610
16635
|
async (args) => {
|
|
16611
16636
|
const frontmatter = {
|
|
@@ -16615,7 +16640,9 @@ function createContributionTools(store) {
|
|
|
16615
16640
|
contributionType: args.contributionType
|
|
16616
16641
|
};
|
|
16617
16642
|
frontmatter.aboutArtifact = args.aboutArtifact;
|
|
16618
|
-
|
|
16643
|
+
const tags = [...args.tags ?? []];
|
|
16644
|
+
if (args.workStream) tags.push(`stream:${args.workStream}`);
|
|
16645
|
+
if (tags.length > 0) frontmatter.tags = tags;
|
|
16619
16646
|
const doc = store.create("contribution", frontmatter, args.content);
|
|
16620
16647
|
return {
|
|
16621
16648
|
content: [
|
|
@@ -16634,10 +16661,18 @@ function createContributionTools(store) {
|
|
|
16634
16661
|
id: external_exports.string().describe("Contribution ID to update"),
|
|
16635
16662
|
title: external_exports.string().optional().describe("New title"),
|
|
16636
16663
|
status: external_exports.string().optional().describe("New status"),
|
|
16637
|
-
content: external_exports.string().optional().describe("New content")
|
|
16664
|
+
content: external_exports.string().optional().describe("New content"),
|
|
16665
|
+
workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Replaces existing stream:<value> tag.")
|
|
16638
16666
|
},
|
|
16639
16667
|
async (args) => {
|
|
16640
|
-
const { id, content, ...updates } = args;
|
|
16668
|
+
const { id, content, workStream, ...updates } = args;
|
|
16669
|
+
if (workStream !== void 0) {
|
|
16670
|
+
const existing = store.get(id);
|
|
16671
|
+
const existingTags = existing?.frontmatter.tags ?? [];
|
|
16672
|
+
const filtered = existingTags.filter((t) => !t.startsWith("stream:"));
|
|
16673
|
+
filtered.push(`stream:${workStream}`);
|
|
16674
|
+
updates.tags = filtered;
|
|
16675
|
+
}
|
|
16641
16676
|
const doc = store.update(id, updates, content);
|
|
16642
16677
|
return {
|
|
16643
16678
|
content: [
|
|
@@ -17105,7 +17140,8 @@ function createTaskTools(store) {
|
|
|
17105
17140
|
estimatedPoints: external_exports.number().optional().describe("Story point estimate"),
|
|
17106
17141
|
complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("Task complexity"),
|
|
17107
17142
|
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("Task priority"),
|
|
17108
|
-
tags: external_exports.array(external_exports.string()).optional().describe("Additional tags")
|
|
17143
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Additional tags"),
|
|
17144
|
+
workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Adds a stream:<value> tag.")
|
|
17109
17145
|
},
|
|
17110
17146
|
async (args) => {
|
|
17111
17147
|
const linkedEpics = normalizeLinkedEpics(args.linkedEpic);
|
|
@@ -17118,11 +17154,13 @@ function createTaskTools(store) {
|
|
|
17118
17154
|
warnings.push(`Warning: ${epicId} is a ${epic.frontmatter.type}, not an epic`);
|
|
17119
17155
|
}
|
|
17120
17156
|
}
|
|
17157
|
+
const baseTags = [...generateEpicTags(linkedEpics), ...args.tags ?? []];
|
|
17158
|
+
if (args.workStream) baseTags.push(`stream:${args.workStream}`);
|
|
17121
17159
|
const frontmatter = {
|
|
17122
17160
|
title: args.title,
|
|
17123
17161
|
status: args.status ?? "backlog",
|
|
17124
17162
|
linkedEpic: linkedEpics,
|
|
17125
|
-
tags:
|
|
17163
|
+
tags: baseTags
|
|
17126
17164
|
};
|
|
17127
17165
|
if (args.aboutArtifact) frontmatter.aboutArtifact = args.aboutArtifact;
|
|
17128
17166
|
if (args.acceptanceCriteria) frontmatter.acceptanceCriteria = args.acceptanceCriteria;
|
|
@@ -17157,10 +17195,11 @@ function createTaskTools(store) {
|
|
|
17157
17195
|
estimatedPoints: external_exports.number().optional().describe("New story point estimate"),
|
|
17158
17196
|
complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("New complexity"),
|
|
17159
17197
|
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority"),
|
|
17160
|
-
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove old tags, add new ones)")
|
|
17198
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove old tags, add new ones)"),
|
|
17199
|
+
workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Replaces existing stream:<value> tag.")
|
|
17161
17200
|
},
|
|
17162
17201
|
async (args) => {
|
|
17163
|
-
const { id, content, linkedEpic: rawLinkedEpic, tags: userTags, ...updates } = args;
|
|
17202
|
+
const { id, content, linkedEpic: rawLinkedEpic, tags: userTags, workStream, ...updates } = args;
|
|
17164
17203
|
const warnings = [];
|
|
17165
17204
|
if (rawLinkedEpic !== void 0) {
|
|
17166
17205
|
const linkedEpics = normalizeLinkedEpics(rawLinkedEpic);
|
|
@@ -17181,6 +17220,12 @@ function createTaskTools(store) {
|
|
|
17181
17220
|
} else if (userTags !== void 0) {
|
|
17182
17221
|
updates.tags = userTags;
|
|
17183
17222
|
}
|
|
17223
|
+
if (workStream !== void 0) {
|
|
17224
|
+
const currentTags = updates.tags ?? store.get(id)?.frontmatter.tags ?? [];
|
|
17225
|
+
const filtered = currentTags.filter((t) => !t.startsWith("stream:"));
|
|
17226
|
+
filtered.push(`stream:${workStream}`);
|
|
17227
|
+
updates.tags = filtered;
|
|
17228
|
+
}
|
|
17184
17229
|
const doc = store.update(id, updates, content);
|
|
17185
17230
|
const parts = [`Updated task ${doc.frontmatter.id}: ${doc.frontmatter.title}`];
|
|
17186
17231
|
if (warnings.length > 0) {
|
|
@@ -20210,6 +20255,12 @@ a:hover { text-decoration: underline; }
|
|
|
20210
20255
|
.badge-closed, .badge-resolved { background: rgba(52, 211, 153, 0.15); color: var(--green); }
|
|
20211
20256
|
.badge-blocked { background: rgba(248, 113, 113, 0.15); color: var(--red); }
|
|
20212
20257
|
.badge-default { background: rgba(139, 143, 164, 0.1); color: var(--text-dim); }
|
|
20258
|
+
.badge-subtle {
|
|
20259
|
+
background: rgba(139, 143, 164, 0.12);
|
|
20260
|
+
color: var(--text-dim);
|
|
20261
|
+
text-transform: none;
|
|
20262
|
+
font-weight: 500;
|
|
20263
|
+
}
|
|
20213
20264
|
|
|
20214
20265
|
/* Table */
|
|
20215
20266
|
.table-wrap {
|
|
@@ -21026,6 +21077,22 @@ tr:hover td {
|
|
|
21026
21077
|
max-height: 0;
|
|
21027
21078
|
opacity: 0;
|
|
21028
21079
|
}
|
|
21080
|
+
|
|
21081
|
+
/* Sortable table headers */
|
|
21082
|
+
.sortable-th {
|
|
21083
|
+
cursor: pointer;
|
|
21084
|
+
user-select: none;
|
|
21085
|
+
}
|
|
21086
|
+
.sortable-th:hover {
|
|
21087
|
+
text-decoration: underline;
|
|
21088
|
+
color: var(--text);
|
|
21089
|
+
}
|
|
21090
|
+
.sort-arrow {
|
|
21091
|
+
display: inline-block;
|
|
21092
|
+
margin-left: 0.3rem;
|
|
21093
|
+
font-size: 0.65rem;
|
|
21094
|
+
opacity: 0.7;
|
|
21095
|
+
}
|
|
21029
21096
|
`;
|
|
21030
21097
|
}
|
|
21031
21098
|
|
|
@@ -21947,16 +22014,56 @@ function sprintSummaryPage(data, cached2) {
|
|
|
21947
22014
|
</div>`,
|
|
21948
22015
|
{ titleTag: "h3" }
|
|
21949
22016
|
) : "";
|
|
22017
|
+
const STREAM_PALETTE = [
|
|
22018
|
+
"hsla(220, 30%, 22%, 0.45)",
|
|
22019
|
+
"hsla(160, 30%, 20%, 0.45)",
|
|
22020
|
+
"hsla(280, 25%, 22%, 0.45)",
|
|
22021
|
+
"hsla(30, 35%, 22%, 0.45)",
|
|
22022
|
+
"hsla(340, 25%, 22%, 0.45)",
|
|
22023
|
+
"hsla(190, 30%, 20%, 0.45)",
|
|
22024
|
+
"hsla(60, 25%, 20%, 0.45)",
|
|
22025
|
+
"hsla(120, 20%, 20%, 0.45)"
|
|
22026
|
+
];
|
|
22027
|
+
function hashString(s) {
|
|
22028
|
+
let h = 0;
|
|
22029
|
+
for (let i = 0; i < s.length; i++) {
|
|
22030
|
+
h = (h << 5) - h + s.charCodeAt(i) | 0;
|
|
22031
|
+
}
|
|
22032
|
+
return Math.abs(h);
|
|
22033
|
+
}
|
|
22034
|
+
function collectStreams(items) {
|
|
22035
|
+
const streams = /* @__PURE__ */ new Set();
|
|
22036
|
+
for (const w of items) {
|
|
22037
|
+
if (w.workStream) streams.add(w.workStream);
|
|
22038
|
+
if (w.children) {
|
|
22039
|
+
for (const s of collectStreams(w.children)) streams.add(s);
|
|
22040
|
+
}
|
|
22041
|
+
}
|
|
22042
|
+
return streams;
|
|
22043
|
+
}
|
|
22044
|
+
const uniqueStreams = collectStreams(data.workItems.items);
|
|
22045
|
+
const streamColorMap = /* @__PURE__ */ new Map();
|
|
22046
|
+
for (const name of uniqueStreams) {
|
|
22047
|
+
streamColorMap.set(name, STREAM_PALETTE[hashString(name) % STREAM_PALETTE.length]);
|
|
22048
|
+
}
|
|
22049
|
+
const streamStyleRules = [...streamColorMap.entries()].map(([name, color]) => `tr[data-stream="${escapeHtml(name)}"] td { background: ${color}; }`).join("\n");
|
|
22050
|
+
const streamStyleBlock = streamStyleRules ? `<style>${streamStyleRules}</style>` : "";
|
|
21950
22051
|
function renderItemRows(items, depth = 0) {
|
|
21951
22052
|
return items.flatMap((w) => {
|
|
21952
22053
|
const isChild = depth > 0;
|
|
21953
22054
|
const isContribution = w.type === "contribution";
|
|
21954
|
-
const
|
|
22055
|
+
const classes = [];
|
|
22056
|
+
if (isContribution) classes.push("contribution-row");
|
|
22057
|
+
else if (isChild) classes.push("child-row");
|
|
22058
|
+
const dataStream = w.workStream ? ` data-stream="${escapeHtml(w.workStream)}"` : "";
|
|
22059
|
+
const rowAttrs = classes.length > 0 ? ` class="${classes.join(" ")}"` : "";
|
|
21955
22060
|
const indent = depth > 0 ? ` style="padding-left: ${0.75 + depth * 1}rem"` : "";
|
|
22061
|
+
const streamCell = w.workStream ? `<span class="badge badge-subtle">${escapeHtml(w.workStream)}</span>` : "";
|
|
21956
22062
|
const row = `
|
|
21957
|
-
<tr${
|
|
22063
|
+
<tr${rowAttrs}${dataStream}>
|
|
21958
22064
|
<td${indent}><a href="/docs/${escapeHtml(w.type)}/${escapeHtml(w.id)}">${escapeHtml(w.id)}</a></td>
|
|
21959
22065
|
<td>${escapeHtml(w.title)}</td>
|
|
22066
|
+
<td>${streamCell}</td>
|
|
21960
22067
|
<td>${escapeHtml(typeLabel(w.type))}</td>
|
|
21961
22068
|
<td>${statusBadge(w.status)}</td>
|
|
21962
22069
|
</tr>`;
|
|
@@ -21965,13 +22072,21 @@ function sprintSummaryPage(data, cached2) {
|
|
|
21965
22072
|
});
|
|
21966
22073
|
}
|
|
21967
22074
|
const workItemRows = renderItemRows(data.workItems.items);
|
|
22075
|
+
const sortableHeaders = `<tr>
|
|
22076
|
+
<th class="sortable-th" onclick="sortWorkItems(0)">ID<span class="sort-arrow" id="sort-arrow-0"></span></th>
|
|
22077
|
+
<th class="sortable-th" onclick="sortWorkItems(1)">Title<span class="sort-arrow" id="sort-arrow-1"></span></th>
|
|
22078
|
+
<th class="sortable-th" onclick="sortWorkItems(2)">Stream<span class="sort-arrow" id="sort-arrow-2"></span></th>
|
|
22079
|
+
<th class="sortable-th" onclick="sortWorkItems(3)">Type<span class="sort-arrow" id="sort-arrow-3"></span></th>
|
|
22080
|
+
<th class="sortable-th" onclick="sortWorkItems(4)">Status<span class="sort-arrow" id="sort-arrow-4"></span></th>
|
|
22081
|
+
</tr>`;
|
|
21968
22082
|
const workItemsSection = workItemRows.length > 0 ? collapsibleSection(
|
|
21969
22083
|
"ss-work-items",
|
|
21970
22084
|
"Work Items",
|
|
21971
|
-
|
|
21972
|
-
|
|
22085
|
+
`${streamStyleBlock}
|
|
22086
|
+
<div class="table-wrap">
|
|
22087
|
+
<table id="work-items-table">
|
|
21973
22088
|
<thead>
|
|
21974
|
-
|
|
22089
|
+
${sortableHeaders}
|
|
21975
22090
|
</thead>
|
|
21976
22091
|
<tbody>
|
|
21977
22092
|
${workItemRows.join("")}
|
|
@@ -22050,6 +22165,61 @@ function sprintSummaryPage(data, cached2) {
|
|
|
22050
22165
|
</div>
|
|
22051
22166
|
|
|
22052
22167
|
<script>
|
|
22168
|
+
var _sortCol = -1;
|
|
22169
|
+
var _sortAsc = true;
|
|
22170
|
+
|
|
22171
|
+
function sortWorkItems(col) {
|
|
22172
|
+
var table = document.getElementById('work-items-table');
|
|
22173
|
+
if (!table) return;
|
|
22174
|
+
var tbody = table.querySelector('tbody');
|
|
22175
|
+
var allRows = Array.from(tbody.querySelectorAll('tr'));
|
|
22176
|
+
|
|
22177
|
+
// Toggle direction if clicking the same column
|
|
22178
|
+
if (_sortCol === col) {
|
|
22179
|
+
_sortAsc = !_sortAsc;
|
|
22180
|
+
} else {
|
|
22181
|
+
_sortCol = col;
|
|
22182
|
+
_sortAsc = true;
|
|
22183
|
+
}
|
|
22184
|
+
|
|
22185
|
+
// Update sort arrows
|
|
22186
|
+
for (var i = 0; i < 5; i++) {
|
|
22187
|
+
var arrow = document.getElementById('sort-arrow-' + i);
|
|
22188
|
+
if (arrow) arrow.textContent = i === col ? (_sortAsc ? ' \\u25B2' : ' \\u25BC') : '';
|
|
22189
|
+
}
|
|
22190
|
+
|
|
22191
|
+
// Group rows: root rows + their child/contribution rows
|
|
22192
|
+
var groups = [];
|
|
22193
|
+
var current = null;
|
|
22194
|
+
for (var r = 0; r < allRows.length; r++) {
|
|
22195
|
+
var row = allRows[r];
|
|
22196
|
+
var isChild = row.classList.contains('child-row') || row.classList.contains('contribution-row');
|
|
22197
|
+
if (!isChild) {
|
|
22198
|
+
current = { root: row, children: [] };
|
|
22199
|
+
groups.push(current);
|
|
22200
|
+
} else if (current) {
|
|
22201
|
+
current.children.push(row);
|
|
22202
|
+
}
|
|
22203
|
+
}
|
|
22204
|
+
|
|
22205
|
+
// Sort groups by root row text content of target column
|
|
22206
|
+
groups.sort(function(a, b) {
|
|
22207
|
+
var aText = (a.root.children[col] ? a.root.children[col].textContent : '').trim().toLowerCase();
|
|
22208
|
+
var bText = (b.root.children[col] ? b.root.children[col].textContent : '').trim().toLowerCase();
|
|
22209
|
+
if (aText < bText) return _sortAsc ? -1 : 1;
|
|
22210
|
+
if (aText > bText) return _sortAsc ? 1 : -1;
|
|
22211
|
+
return 0;
|
|
22212
|
+
});
|
|
22213
|
+
|
|
22214
|
+
// Re-append rows in sorted order
|
|
22215
|
+
for (var g = 0; g < groups.length; g++) {
|
|
22216
|
+
tbody.appendChild(groups[g].root);
|
|
22217
|
+
for (var c = 0; c < groups[g].children.length; c++) {
|
|
22218
|
+
tbody.appendChild(groups[g].children[c]);
|
|
22219
|
+
}
|
|
22220
|
+
}
|
|
22221
|
+
}
|
|
22222
|
+
|
|
22053
22223
|
async function generateSummary() {
|
|
22054
22224
|
var btn = document.getElementById('generate-btn');
|
|
22055
22225
|
var loading = document.getElementById('summary-loading');
|