@gmickel/gno 0.31.1 → 0.32.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmickel/gno",
3
- "version": "0.31.1",
3
+ "version": "0.32.0",
4
4
  "description": "Local semantic search for your documents. Index Markdown, PDF, and Office files with hybrid BM25 + vector search.",
5
5
  "keywords": [
6
6
  "embeddings",
@@ -80,7 +80,7 @@
80
80
  "research:finetune:autonomous:confirm-winner": "bun research/finetune/autonomous/scripts/confirm-winner.ts",
81
81
  "research:finetune:autonomous:check-promotion-targets": "bun research/finetune/autonomous/scripts/check-promotion-targets.ts",
82
82
  "research:finetune:validate": "bun research/finetune/scripts/validate-sandbox.ts",
83
- "research:finetune:qmd-import": "bun research/finetune/scripts/import-qmd-training.ts",
83
+ "research:finetune:qmd-import:legacy": "bun research/finetune/scripts/import-qmd-training.ts",
84
84
  "research:finetune:mlx:build-dataset": "bun research/finetune/scripts/build-mlx-dataset.ts",
85
85
  "research:finetune:build-variant-dataset": "bun research/finetune/scripts/build-variant-dataset.ts",
86
86
  "research:finetune:list-mix-variants": "bun research/finetune/scripts/list-mix-variants.ts",
package/src/serve/jobs.ts CHANGED
@@ -37,6 +37,7 @@ export interface StartJobError {
37
37
  ok: false;
38
38
  error: string;
39
39
  status: 409;
40
+ activeJobId: string;
40
41
  }
41
42
 
42
43
  export type StartJobResult = StartJobSuccess | StartJobError;
@@ -84,6 +85,7 @@ export function startJob(
84
85
  ok: false,
85
86
  error: `Job ${activeJobId} already running`,
86
87
  status: 409,
88
+ activeJobId,
87
89
  };
88
90
  }
89
91
 
@@ -142,6 +144,16 @@ export function getActiveJobId(): string | null {
142
144
  return activeJobId;
143
145
  }
144
146
 
147
+ /**
148
+ * Get the currently active job status, if any.
149
+ */
150
+ export function getActiveJob(): JobStatus | null {
151
+ if (!activeJobId) {
152
+ return null;
153
+ }
154
+ return jobs.get(activeJobId) ?? null;
155
+ }
156
+
145
157
  /**
146
158
  * Update job progress (called from within job execution).
147
159
  *
@@ -167,7 +167,9 @@ function BacklinkItem({
167
167
  <Tooltip>
168
168
  <TooltipTrigger asChild>
169
169
  <div className="min-w-0 flex-1">
170
- <span className="block truncate">{displayName}</span>
170
+ <span className="block break-words font-medium leading-tight whitespace-normal">
171
+ {displayName}
172
+ </span>
171
173
  {/* Line reference as subtitle */}
172
174
  <span className="block truncate text-[10px] opacity-60">
173
175
  L{backlink.startLine}:{backlink.startCol}
@@ -170,7 +170,9 @@ function LinkItem({
170
170
  <Tooltip>
171
171
  <TooltipTrigger asChild>
172
172
  <div className="min-w-0 flex-1">
173
- <span className="block truncate">{displayText}</span>
173
+ <span className="block break-words font-medium leading-tight whitespace-normal">
174
+ {displayText}
175
+ </span>
174
176
  {link.targetAnchor && (
175
177
  <span className="block truncate text-[10px] opacity-60">
176
178
  #{link.targetAnchor}
@@ -260,7 +260,7 @@ function RelatedNoteItem({
260
260
  {/* Title with tooltip for long names */}
261
261
  <Tooltip>
262
262
  <TooltipTrigger asChild>
263
- <h4 className="truncate font-mono text-[13px] text-foreground/90 group-hover:text-foreground">
263
+ <h4 className="line-clamp-3 break-words font-mono text-[13px] leading-tight text-foreground/90 group-hover:text-foreground">
264
264
  {doc.title || "Untitled"}
265
265
  </h4>
266
266
  </TooltipTrigger>
@@ -443,12 +443,12 @@ export default function Browse({ navigate }: PageProps) {
443
443
  {/* Document Table */}
444
444
  {docs.length > 0 && (
445
445
  <div className="animate-fade-in opacity-0">
446
- <Table>
446
+ <Table className="table-fixed">
447
447
  <TableHeader>
448
448
  <TableRow>
449
- <TableHead className="w-[50%]">Document</TableHead>
450
- <TableHead>Collection</TableHead>
451
- <TableHead className="text-right">Type</TableHead>
449
+ <TableHead className="w-[68%]">Document</TableHead>
450
+ <TableHead className="w-[220px]">Collection</TableHead>
451
+ <TableHead className="w-[72px] text-right">Type</TableHead>
452
452
  </TableRow>
453
453
  </TableHeader>
454
454
  <TableBody>
@@ -460,14 +460,14 @@ export default function Browse({ navigate }: PageProps) {
460
460
  navigate(`/doc?uri=${encodeURIComponent(doc.uri)}`)
461
461
  }
462
462
  >
463
- <TableCell>
463
+ <TableCell className="align-top whitespace-normal">
464
464
  <div className="flex items-center gap-2">
465
465
  <FileText className="size-4 shrink-0 text-muted-foreground" />
466
466
  <div className="min-w-0">
467
- <div className="truncate font-medium transition-colors group-hover:text-primary">
467
+ <div className="break-words font-medium leading-tight transition-colors group-hover:text-primary">
468
468
  {doc.title || doc.relPath}
469
469
  </div>
470
- <div className="truncate font-mono text-muted-foreground text-xs">
470
+ <div className="break-all font-mono text-muted-foreground text-xs leading-relaxed">
471
471
  {doc.relPath}
472
472
  </div>
473
473
  </div>
@@ -499,9 +499,9 @@ export default function Browse({ navigate }: PageProps) {
499
499
  <ChevronRight className="ml-auto size-4 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100" />
500
500
  </div>
501
501
  </TableCell>
502
- <TableCell>
502
+ <TableCell className="align-top whitespace-normal">
503
503
  <Badge
504
- className="cursor-pointer font-mono text-xs transition-colors hover:border-primary hover:text-primary"
504
+ className="inline-flex min-h-[2.5rem] max-w-[180px] cursor-pointer items-center px-3 py-1 text-center whitespace-normal break-words font-mono text-xs leading-tight transition-colors hover:border-primary hover:text-primary"
505
505
  onClick={(event) => {
506
506
  event.stopPropagation();
507
507
  navigateToCollection(doc.collection);
@@ -511,7 +511,7 @@ export default function Browse({ navigate }: PageProps) {
511
511
  {doc.collection}
512
512
  </Badge>
513
513
  </TableCell>
514
- <TableCell className="text-right">
514
+ <TableCell className="align-top text-right">
515
515
  <Badge
516
516
  className="font-mono text-xs"
517
517
  variant={getExtBadgeVariant(doc.sourceExt)}
@@ -17,6 +17,7 @@ import type { SqliteAdapter } from "../../store/sqlite/adapter";
17
17
  import type { DocumentRow } from "../../store/types";
18
18
  import type { DocumentEventBus } from "../doc-events";
19
19
  import type { EmbedScheduler } from "../embed-scheduler";
20
+ import type { StartJobError } from "../jobs";
20
21
  import type { CollectionWatchService } from "../watch-service";
21
22
 
22
23
  import { modelsPull } from "../../cli/commands/models/pull";
@@ -58,7 +59,7 @@ import {
58
59
  type ServerContext,
59
60
  } from "../context";
60
61
  import { analyzeImportPath } from "../import-preview";
61
- import { getJobStatus, startJob } from "../jobs";
62
+ import { getActiveJob, getJobStatus, startJob } from "../jobs";
62
63
  import { buildAppStatus, type StatusBuildDeps } from "../status";
63
64
 
64
65
  /** Mutable context holder for hot-reloading presets */
@@ -78,6 +79,7 @@ export interface ApiError {
78
79
  error: {
79
80
  code: string;
80
81
  message: string;
82
+ details?: Record<string, unknown>;
81
83
  };
82
84
  }
83
85
 
@@ -211,8 +213,28 @@ function jsonResponse(data: unknown, status = 200): Response {
211
213
  return Response.json(data, { status });
212
214
  }
213
215
 
214
- function errorResponse(code: string, message: string, status = 400): Response {
215
- return jsonResponse({ error: { code, message } }, status);
216
+ function errorResponse(
217
+ code: string,
218
+ message: string,
219
+ status = 400,
220
+ details?: Record<string, unknown>
221
+ ): Response {
222
+ return jsonResponse(
223
+ {
224
+ error: {
225
+ code,
226
+ message,
227
+ ...(details ? { details } : {}),
228
+ },
229
+ },
230
+ status
231
+ );
232
+ }
233
+
234
+ function jobConflictResponse(jobResult: StartJobError): Response {
235
+ return errorResponse("CONFLICT", jobResult.error, 409, {
236
+ activeJobId: jobResult.activeJobId,
237
+ });
216
238
  }
217
239
 
218
240
  function parseCommaSeparatedValues(input: string): string[] {
@@ -591,7 +613,7 @@ export async function handleCreateCollection(
591
613
  });
592
614
 
593
615
  if (!jobResult.ok) {
594
- return errorResponse("CONFLICT", jobResult.error, 409);
616
+ return jobConflictResponse(jobResult);
595
617
  }
596
618
 
597
619
  return jsonResponse(
@@ -742,7 +764,7 @@ export async function handleSync(
742
764
  });
743
765
 
744
766
  if (!jobResult.ok) {
745
- return errorResponse("CONFLICT", jobResult.error, 409);
767
+ return jobConflictResponse(jobResult);
746
768
  }
747
769
 
748
770
  return jsonResponse({ jobId: jobResult.jobId }, 202);
@@ -2619,6 +2641,16 @@ export function handleJob(jobId: string): Response {
2619
2641
  return jsonResponse(status);
2620
2642
  }
2621
2643
 
2644
+ /**
2645
+ * GET /api/jobs/active
2646
+ * Returns the current active job, or null when idle.
2647
+ */
2648
+ export function handleActiveJob(): Response {
2649
+ return jsonResponse({
2650
+ activeJob: getActiveJob(),
2651
+ });
2652
+ }
2653
+
2622
2654
  // ─────────────────────────────────────────────────────────────────────────────
2623
2655
  // Embed Scheduler
2624
2656
  // ─────────────────────────────────────────────────────────────────────────────
@@ -13,6 +13,7 @@ import { DocumentEventBus } from "./doc-events";
13
13
  // HTML import - Bun handles bundling TSX/CSS automatically via routes
14
14
  import homepage from "./public/index.html";
15
15
  import {
16
+ handleActiveJob,
16
17
  handleAsk,
17
18
  handleCapabilities,
18
19
  handleCollections,
@@ -421,6 +422,9 @@ export async function startServer(
421
422
  return withSecurityHeaders(handleModelPull(ctxHolder), isDev);
422
423
  },
423
424
  },
425
+ "/api/jobs/active": {
426
+ GET: () => withSecurityHeaders(handleActiveJob(), isDev),
427
+ },
424
428
  "/api/jobs/:id": {
425
429
  GET: (req: Request) => {
426
430
  const url = new URL(req.url);