hotsheet 0.6.4 → 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 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()) tagSet.add(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
- lines.push(`- Tags: ${tags.join(", ")}`);
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) => {