hotsheet 0.6.3 → 0.6.5
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 +4 -0
- package/dist/cli.js +135 -6
- package/dist/client/app.global.js +43 -43
- package/dist/client/styles.css +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -162,6 +162,10 @@ Hot Sheet can push events directly to a running Claude Code session via MCP chan
|
|
|
162
162
|
|
|
163
163
|
Requires Claude Code v2.1.80+ with channel support. See [docs/12-claude-channel.md](docs/12-claude-channel.md) for setup details.
|
|
164
164
|
|
|
165
|
+
<p align="center">
|
|
166
|
+
<img src="docs/demo-9.png" alt="Claude Channel integration with play button, custom command buttons, and AI-driven workflow" width="900">
|
|
167
|
+
</p>
|
|
168
|
+
|
|
165
169
|
### Other AI Tools
|
|
166
170
|
|
|
167
171
|
The worklist works with any AI tool that reads files — Cursor, Copilot, Aider, etc. Each ticket includes its number, type, priority, status, title, and details.
|
package/dist/cli.js
CHANGED
|
@@ -865,6 +865,10 @@ async function createTicket(title, defaults) {
|
|
|
865
865
|
cols.push("details");
|
|
866
866
|
vals.push(defaults.details);
|
|
867
867
|
}
|
|
868
|
+
if (defaults?.tags !== void 0 && defaults.tags !== "" && defaults.tags !== "[]") {
|
|
869
|
+
cols.push("tags");
|
|
870
|
+
vals.push(defaults.tags);
|
|
871
|
+
}
|
|
868
872
|
const placeholders = vals.map((_, i) => `$${i + 1}`).join(", ");
|
|
869
873
|
const result = await db2.query(
|
|
870
874
|
`INSERT INTO tickets (${cols.join(", ")}) VALUES (${placeholders}) RETURNING *`,
|
|
@@ -1098,7 +1102,7 @@ function ordinalValue(field, value) {
|
|
|
1098
1102
|
if (field === "status") return STATUS_RANK[value] ?? null;
|
|
1099
1103
|
return null;
|
|
1100
1104
|
}
|
|
1101
|
-
async function queryTickets(logic, conditions, sortBy, sortDir) {
|
|
1105
|
+
async function queryTickets(logic, conditions, sortBy, sortDir, requiredTag) {
|
|
1102
1106
|
const db2 = await getDb();
|
|
1103
1107
|
const where = [];
|
|
1104
1108
|
const values = [];
|
|
@@ -1145,6 +1149,11 @@ async function queryTickets(logic, conditions, sortBy, sortDir) {
|
|
|
1145
1149
|
break;
|
|
1146
1150
|
}
|
|
1147
1151
|
}
|
|
1152
|
+
if (requiredTag) {
|
|
1153
|
+
where[0] += ` AND tags ILIKE $${paramIdx}`;
|
|
1154
|
+
values.push(`%${requiredTag}%`);
|
|
1155
|
+
paramIdx++;
|
|
1156
|
+
}
|
|
1148
1157
|
const joiner = logic === "any" ? " OR " : " AND ";
|
|
1149
1158
|
const userConditions = where.slice(1);
|
|
1150
1159
|
let whereClause = where[0];
|
|
@@ -1173,6 +1182,9 @@ async function queryTickets(logic, conditions, sortBy, sortDir) {
|
|
|
1173
1182
|
);
|
|
1174
1183
|
return result.rows;
|
|
1175
1184
|
}
|
|
1185
|
+
function normalizeTag(input) {
|
|
1186
|
+
return input.replace(/[^a-zA-Z0-9]+/g, " ").trim().toLowerCase();
|
|
1187
|
+
}
|
|
1176
1188
|
async function getAllTags() {
|
|
1177
1189
|
const db2 = await getDb();
|
|
1178
1190
|
const result = await db2.query(`SELECT DISTINCT tags FROM tickets WHERE tags != '[]' AND status != 'deleted'`);
|
|
@@ -1182,7 +1194,10 @@ async function getAllTags() {
|
|
|
1182
1194
|
const parsed = JSON.parse(row.tags);
|
|
1183
1195
|
if (Array.isArray(parsed)) {
|
|
1184
1196
|
for (const tag of parsed) {
|
|
1185
|
-
if (typeof tag === "string" && tag.trim())
|
|
1197
|
+
if (typeof tag === "string" && tag.trim()) {
|
|
1198
|
+
const norm = normalizeTag(tag);
|
|
1199
|
+
if (norm) tagSet.add(norm);
|
|
1200
|
+
}
|
|
1186
1201
|
}
|
|
1187
1202
|
}
|
|
1188
1203
|
} catch {
|
|
@@ -1360,7 +1375,8 @@ var DEMO_SCENARIOS = [
|
|
|
1360
1375
|
{ id: 5, label: "Batch operations \u2014 multi-select toolbar" },
|
|
1361
1376
|
{ id: 6, label: "Detail panel \u2014 bottom orientation with tags and notes" },
|
|
1362
1377
|
{ id: 7, label: "Column view \u2014 kanban board by status" },
|
|
1363
|
-
{ id: 8, label: "Dashboard \u2014 stats and charts" }
|
|
1378
|
+
{ id: 8, label: "Dashboard \u2014 stats and charts" },
|
|
1379
|
+
{ id: 9, label: "Claude Channel \u2014 AI integration with custom commands" }
|
|
1364
1380
|
];
|
|
1365
1381
|
function daysAgo(days) {
|
|
1366
1382
|
const d = /* @__PURE__ */ new Date();
|
|
@@ -2144,6 +2160,104 @@ for (let i = 0; i < 30; i++) {
|
|
|
2144
2160
|
verified_ago: verified
|
|
2145
2161
|
});
|
|
2146
2162
|
}
|
|
2163
|
+
var SCENARIO_9 = [
|
|
2164
|
+
{
|
|
2165
|
+
title: "Fix race condition in WebSocket message ordering",
|
|
2166
|
+
details: "Messages arriving during reconnect can be delivered out of order. Need to add sequence numbers and a reorder buffer on the client side.\n\nReproduction: disconnect WiFi briefly during a burst of real-time updates, then reconnect \u2014 events appear in wrong order.",
|
|
2167
|
+
category: "bug",
|
|
2168
|
+
priority: "highest",
|
|
2169
|
+
status: "started",
|
|
2170
|
+
up_next: true,
|
|
2171
|
+
tags: ["websocket", "real-time"],
|
|
2172
|
+
notes: notesJson([
|
|
2173
|
+
{ text: "Investigating \u2014 the issue is in the reconnect handler. When the socket reconnects, buffered server-side messages are flushed immediately without checking the client sequence counter.", days_ago: 0.1 }
|
|
2174
|
+
]),
|
|
2175
|
+
days_ago: 3,
|
|
2176
|
+
updated_ago: 0.1
|
|
2177
|
+
},
|
|
2178
|
+
{
|
|
2179
|
+
title: "Add rate limiting to public API endpoints",
|
|
2180
|
+
details: "Implement token bucket rate limiting for all /api/v2/ endpoints. 100 requests per minute per API key, with burst allowance of 20.",
|
|
2181
|
+
category: "feature",
|
|
2182
|
+
priority: "high",
|
|
2183
|
+
status: "not_started",
|
|
2184
|
+
up_next: true,
|
|
2185
|
+
tags: ["api", "security"],
|
|
2186
|
+
notes: "",
|
|
2187
|
+
days_ago: 2,
|
|
2188
|
+
updated_ago: 2
|
|
2189
|
+
},
|
|
2190
|
+
{
|
|
2191
|
+
title: "Migrate user preferences to new schema",
|
|
2192
|
+
details: "The preferences table needs to be migrated from the old key-value format to the new typed JSON column. Write a migration script that preserves existing user settings.",
|
|
2193
|
+
category: "task",
|
|
2194
|
+
priority: "default",
|
|
2195
|
+
status: "not_started",
|
|
2196
|
+
up_next: true,
|
|
2197
|
+
tags: ["database", "migration"],
|
|
2198
|
+
notes: "",
|
|
2199
|
+
days_ago: 4,
|
|
2200
|
+
updated_ago: 4
|
|
2201
|
+
},
|
|
2202
|
+
{
|
|
2203
|
+
title: "Investigate slow query on orders dashboard",
|
|
2204
|
+
details: "The orders dashboard takes 8+ seconds to load for merchants with >10k orders. Need to profile the SQL and add appropriate indexes.",
|
|
2205
|
+
category: "investigation",
|
|
2206
|
+
priority: "high",
|
|
2207
|
+
status: "completed",
|
|
2208
|
+
up_next: false,
|
|
2209
|
+
tags: ["performance", "database"],
|
|
2210
|
+
notes: notesJson([
|
|
2211
|
+
{ text: "Root cause: missing composite index on (merchant_id, created_at). The query was doing a full table scan. Added index and query time dropped from 8.2s to 45ms.", days_ago: 1 }
|
|
2212
|
+
]),
|
|
2213
|
+
days_ago: 5,
|
|
2214
|
+
updated_ago: 1,
|
|
2215
|
+
completed_ago: 1
|
|
2216
|
+
},
|
|
2217
|
+
{
|
|
2218
|
+
title: "Update error handling middleware to use structured logging",
|
|
2219
|
+
details: "Replace console.error calls with structured JSON logging using pino. Include request ID, user context, and stack traces.",
|
|
2220
|
+
category: "task",
|
|
2221
|
+
priority: "default",
|
|
2222
|
+
status: "completed",
|
|
2223
|
+
up_next: false,
|
|
2224
|
+
tags: ["observability", "logging"],
|
|
2225
|
+
notes: notesJson([
|
|
2226
|
+
{ text: "Replaced all console.error/warn calls with pino logger. Added request ID propagation via AsyncLocalStorage. Error responses now include a correlationId for support debugging.", days_ago: 2 }
|
|
2227
|
+
]),
|
|
2228
|
+
days_ago: 7,
|
|
2229
|
+
updated_ago: 2,
|
|
2230
|
+
completed_ago: 2
|
|
2231
|
+
},
|
|
2232
|
+
{
|
|
2233
|
+
title: "Fix CORS headers missing on preflight for webhook endpoints",
|
|
2234
|
+
details: "Third-party integrations sending OPTIONS preflight requests to /webhooks/* get 405 Method Not Allowed.",
|
|
2235
|
+
category: "bug",
|
|
2236
|
+
priority: "default",
|
|
2237
|
+
status: "verified",
|
|
2238
|
+
up_next: false,
|
|
2239
|
+
tags: ["api", "webhooks"],
|
|
2240
|
+
notes: notesJson([
|
|
2241
|
+
{ text: "Added CORS preflight handler for webhook routes. Configured allowed origins from the integration settings table.", days_ago: 4 }
|
|
2242
|
+
]),
|
|
2243
|
+
days_ago: 9,
|
|
2244
|
+
updated_ago: 4,
|
|
2245
|
+
completed_ago: 5,
|
|
2246
|
+
verified_ago: 4
|
|
2247
|
+
},
|
|
2248
|
+
{
|
|
2249
|
+
title: "Design new onboarding flow for team workspaces",
|
|
2250
|
+
details: "The current onboarding drops users into an empty workspace. Design a guided setup that creates sample data and walks through key features.",
|
|
2251
|
+
category: "feature",
|
|
2252
|
+
priority: "low",
|
|
2253
|
+
status: "not_started",
|
|
2254
|
+
up_next: false,
|
|
2255
|
+
tags: ["onboarding", "ux"],
|
|
2256
|
+
notes: "",
|
|
2257
|
+
days_ago: 10,
|
|
2258
|
+
updated_ago: 10
|
|
2259
|
+
}
|
|
2260
|
+
];
|
|
2147
2261
|
var SCENARIO_DATA = {
|
|
2148
2262
|
1: SCENARIO_1,
|
|
2149
2263
|
2: SCENARIO_2,
|
|
@@ -2152,7 +2266,8 @@ var SCENARIO_DATA = {
|
|
|
2152
2266
|
5: SCENARIO_5,
|
|
2153
2267
|
6: SCENARIO_6,
|
|
2154
2268
|
7: SCENARIO_7,
|
|
2155
|
-
8: SCENARIO_8
|
|
2269
|
+
8: SCENARIO_8,
|
|
2270
|
+
9: SCENARIO_9
|
|
2156
2271
|
};
|
|
2157
2272
|
var SCENARIO_3_VIEWS = [
|
|
2158
2273
|
{
|
|
@@ -2174,6 +2289,12 @@ var SCENARIO_3_VIEWS = [
|
|
|
2174
2289
|
]
|
|
2175
2290
|
}
|
|
2176
2291
|
];
|
|
2292
|
+
var SCENARIO_9_COMMANDS = [
|
|
2293
|
+
{ name: "Commit Changes", prompt: "Make a commit for the recently completed tickets.", icon: "git-commit-horizontal", color: "#6b7280" },
|
|
2294
|
+
{ name: "Run Tests", prompt: "Run the test suite and report any failures.", icon: "test-tubes", color: "#3b82f6" },
|
|
2295
|
+
{ name: "Code Review", prompt: "Review the recent changes for code quality and potential issues.", icon: "search-code", color: "#8b5cf6" },
|
|
2296
|
+
{ name: "Deploy Staging", prompt: "Deploy the current branch to the staging environment.", icon: "rocket", color: "#f97316" }
|
|
2297
|
+
];
|
|
2177
2298
|
async function seedDemoData(scenario) {
|
|
2178
2299
|
const db2 = await getDb();
|
|
2179
2300
|
const tickets = SCENARIO_DATA[scenario];
|
|
@@ -2210,6 +2331,13 @@ async function seedDemoData(scenario) {
|
|
|
2210
2331
|
await backfillSnapshots2();
|
|
2211
2332
|
await recordDailySnapshot2();
|
|
2212
2333
|
}
|
|
2334
|
+
if (scenario === 9) {
|
|
2335
|
+
await db2.query(`INSERT INTO settings (key, value) VALUES ('channel_enabled', 'true') ON CONFLICT (key) DO UPDATE SET value = 'true'`);
|
|
2336
|
+
await db2.query(
|
|
2337
|
+
`INSERT INTO settings (key, value) VALUES ('custom_commands', $1) ON CONFLICT (key) DO UPDATE SET value = $1`,
|
|
2338
|
+
[JSON.stringify(SCENARIO_9_COMMANDS)]
|
|
2339
|
+
);
|
|
2340
|
+
}
|
|
2213
2341
|
}
|
|
2214
2342
|
|
|
2215
2343
|
// src/cli.ts
|
|
@@ -2526,7 +2654,8 @@ async function formatTicket(ticket) {
|
|
|
2526
2654
|
try {
|
|
2527
2655
|
const tags = JSON.parse(ticket.tags);
|
|
2528
2656
|
if (Array.isArray(tags) && tags.length > 0) {
|
|
2529
|
-
|
|
2657
|
+
const display = tags.map((t) => t.replace(/\b\w/g, (c) => c.toUpperCase()));
|
|
2658
|
+
lines.push(`- Tags: ${display.join(", ")}`);
|
|
2530
2659
|
}
|
|
2531
2660
|
} catch {
|
|
2532
2661
|
}
|
|
@@ -2939,7 +3068,7 @@ apiRoutes.get("/attachments/file/*", async (c) => {
|
|
|
2939
3068
|
});
|
|
2940
3069
|
apiRoutes.post("/tickets/query", async (c) => {
|
|
2941
3070
|
const body = await c.req.json();
|
|
2942
|
-
const tickets = await queryTickets(body.logic, body.conditions, body.sort_by, body.sort_dir);
|
|
3071
|
+
const tickets = await queryTickets(body.logic, body.conditions, body.sort_by, body.sort_dir, body.required_tag);
|
|
2943
3072
|
return c.json(tickets);
|
|
2944
3073
|
});
|
|
2945
3074
|
apiRoutes.get("/tags", async (c) => {
|