@gotcos/glasses-server 6.44.0 → 6.44.1

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/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 6.44.1
2
+
3
+ Board and lens titles are readable.
4
+
5
+ - `projectRow` and `projectTaskRun` sliced the raw `tasks.md` line at 44 characters, so a title kept its markdown and was cut mid-word: 119 of the 201 rows served today began with an unbalanced `**`, and one row read `**Provide data migration process docs by ind`. Both now go through `taskTitle`, which strips code spans, links and emphasis first — buying back the characters the asterisks were spending — then cuts on a word boundary and marks the cut. Verified over all 312 live task lines: none over the cap, none with a stray asterisk, none empty.
6
+ - Underscore emphasis is anchored, so `cos_python` and `hermit_crabs` survive; only `_emphasis_` is stripped.
7
+ - `TASK_TITLE_MAX` is exported so tests pin the real cap instead of a copy of it.
8
+
1
9
  ## 6.44.0
2
10
 
3
11
  Task store: one COS `tasks.md` contract with a durable lock, columns, and a run ledger.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotcos/glasses-server",
3
- "version": "6.44.0",
3
+ "version": "6.44.1",
4
4
  "description": "COS Glasses \u2014 self-hosted AI heads-up-display server for Even G2 smart glasses, powered by Claude Code, Codex, Cursor Agent CLI, or local Ollama",
5
5
  "type": "module",
6
6
  "bin": {
@@ -50,7 +50,9 @@
50
50
  "url": "git+https://github.com/ukaoma/cos-glasses-server.git"
51
51
  },
52
52
  "homepage": "https://www.gotcos.com",
53
- "bugs": { "url": "https://github.com/ukaoma/cos-glasses-server/issues" },
53
+ "bugs": {
54
+ "url": "https://github.com/ukaoma/cos-glasses-server/issues"
55
+ },
54
56
  "publishConfig": {
55
57
  "access": "public"
56
58
  },
@@ -333,6 +333,34 @@ export async function loadAllRows(day: string): Promise<BridgeTaskRow[]> {
333
333
  return groups.flat()
334
334
  }
335
335
 
336
+ /** Board and lens titles come from a raw tasks.md line, which carries markdown
337
+ * emphasis and is usually far longer than a row. Slicing the raw line cut words
338
+ * and left unbalanced `**` on 119 of 201 live rows, so a row read
339
+ * "**Provide data migration process docs by ind". Strip the markup first (which
340
+ * buys back the four characters the asterisks were spending), then cut on a word
341
+ * boundary. The cap is exported so tests pin the real value rather than a copy. */
342
+ export const TASK_TITLE_MAX = 44
343
+
344
+ export function taskTitle(line: string): string {
345
+ const plain = line
346
+ .replace(/`([^`]*)`/g, '$1')
347
+ .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
348
+ // Underscore emphasis must be anchored: a bare `_` between word characters is
349
+ // an identifier (cos_python, hermit_crabs), not markup, and stripping it
350
+ // corrupts the row. Asterisks need no such care, so one catch-all below clears
351
+ // them whether the pair is balanced or not — and after a slice, it often is not.
352
+ .replace(/(^|[\s(])_([^_]+)_(?=[\s).,;:!?]|$)/g, '$1$2')
353
+ .replace(/\*+/g, '')
354
+ .replace(/\s+/g, ' ')
355
+ .trim()
356
+ if (plain.length <= TASK_TITLE_MAX) return plain
357
+ const cut = plain.slice(0, TASK_TITLE_MAX - 1)
358
+ const space = cut.lastIndexOf(' ')
359
+ // Only honour a word boundary that is not so early it throws the title away.
360
+ const body = space > TASK_TITLE_MAX * 0.6 ? cut.slice(0, space) : cut
361
+ return `${body.replace(/[\s\u2014\u2013,;:.-]+$/, '')}\u2026`
362
+ }
363
+
336
364
  export function projectRow(
337
365
  row: BridgeTaskRow,
338
366
  ledger: readonly TaskRun[],
@@ -350,7 +378,7 @@ export function projectRow(
350
378
  id: row.id,
351
379
  ref: row.ref,
352
380
  domain: row.domain,
353
- title: row.description.slice(0, 44),
381
+ title: taskTitle(row.description),
354
382
  column: col,
355
383
  priority: row.priority,
356
384
  ...(runAt ? { runAt: new Date(taskInstant(runAt.day, runAt.minutes, tz)).toISOString() } : {}),
@@ -502,7 +530,7 @@ export function projectTaskRun(
502
530
  run: TaskRun,
503
531
  snapshot?: { status: string; completedAt?: string } | null,
504
532
  ): TaskRunView {
505
- const title = run.line.slice(0, 44)
533
+ const title = taskTitle(run.line)
506
534
  const base = {
507
535
  id: run.id,
508
536
  kind: 'task' as const,