@nanobpm/urban 0.30.0 → 0.31.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.
@@ -580,12 +580,20 @@ function renderText(node) {
580
580
  return el(v === "heading" ? "h1" : "p", { class: cls }, node.props.text || "");
581
581
  }
582
582
 
583
- // A grid td cell. When the column declares linkField, the cell text (the
584
- // column's own value) becomes a link to the URL held in that other field,
585
- // opened in a new tab. Only http(s) hrefs are linked — anything else (e.g. a
586
- // javascript: URL smuggled through row data) falls back to plain text — and
587
- // external links get rel=noopener noreferrer so the opened page can't reach
588
- // window.opener. Shared by the top-level grid and child grids.
583
+ // A grid td cell. Two column-declared linking modes, checked in order:
584
+ // 1. linkField — the cell text becomes a link to the URL held in that other
585
+ // field. Only http(s) hrefs are linked (a javascript:/other-scheme URL
586
+ // smuggled through row data falls back to plain text).
587
+ // 2. link: { kind: "processExplorer", keyField } — a structured, engine-aware
588
+ // link: the cell text links to the Nano console's explorer view for the
589
+ // process instance whose key is held in the row's keyField. The console
590
+ // path is constructed HERE (never taken from row data) and the key is
591
+ // URL-encoded, so row data can't smuggle a path or scheme; only a
592
+ // non-empty key (after trimming surrounding whitespace) produces a link.
593
+ // Unknown link kinds fall back to plain text so an unrecognised schema
594
+ // can't render a broken anchor.
595
+ // Both open in a new tab with rel=noopener noreferrer so the opened page can't
596
+ // reach window.opener. Shared by the top-level grid and child grids.
589
597
  function gridCell(col, row) {
590
598
  const text = row[col.field] == null ? "" : String(row[col.field]);
591
599
  if (col.linkField) {
@@ -598,6 +606,18 @@ function gridCell(col, row) {
598
606
  );
599
607
  }
600
608
  }
609
+ if (col.link && col.link.kind === "processExplorer" && col.link.keyField) {
610
+ const key = row[col.link.keyField];
611
+ const keyStr = key == null ? "" : String(key).trim();
612
+ if (text !== "" && keyStr !== "") {
613
+ const href = "/console/explorer?instance=" + encodeURIComponent(keyStr);
614
+ return el(
615
+ "td",
616
+ {},
617
+ el("a", { class: "pc-link", href, target: "_blank", rel: "noopener noreferrer" }, text),
618
+ );
619
+ }
620
+ }
601
621
  return el("td", {}, text);
602
622
  }
603
623
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/urban",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "description": "Urban: build and run code-first apps on Nano — runtime, derivation toolkit, and CLI in one. Author durable processes with @nanobpm/workflow (defineFlow), a typed datasource, forms, triggers and surfaces from a nano.app.json manifest, on Node or Deno.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -189,6 +189,23 @@ test("renderer wires a column's linkField to a new-tab anchor", async () => {
189
189
  assert.match(js, /rel: "noopener noreferrer"/);
190
190
  });
191
191
 
192
+ test("renderer wires a column's processExplorer link to the console explorer", async () => {
193
+ const res = await dispatch("GET", "/app/runtime.js");
194
+ const js = res.body ?? "";
195
+ // A column declaring `link: { kind: "processExplorer", keyField }` renders its
196
+ // text as a link to the Nano console's explorer for the process instance whose
197
+ // key is held in that field. Guard the whole shape so the primitive can't
198
+ // silently regress: the discriminant + keyField are read, the key is trimmed
199
+ // (whitespace-only keys don't produce a link), the console path is constructed
200
+ // here (not from row data), the trimmed key is URL-encoded, and the anchor
201
+ // opens in a new tab with a hardened rel.
202
+ assert.match(js, /col\.link && col\.link\.kind === "processExplorer" && col\.link\.keyField/);
203
+ assert.match(js, /String\(key\)\.trim\(\)/);
204
+ assert.match(js, /"\/console\/explorer\?instance="\s*\+\s*encodeURIComponent\(keyStr\)/);
205
+ assert.match(js, /target:\s*"_blank"/);
206
+ assert.match(js, /rel:\s*"noopener noreferrer"/);
207
+ });
208
+
192
209
  test("the renderer honours numeric actionForm fields", async () => {
193
210
  const res = await dispatch("GET", "/app/runtime.js");
194
211
  const js = res.body ?? "";
@@ -621,12 +621,20 @@ function renderText(node) {
621
621
  return el(v === "heading" ? "h1" : "p", { class: cls }, node.props.text || "");
622
622
  }
623
623
 
624
- // A grid td cell. When the column declares linkField, the cell text (the
625
- // column's own value) becomes a link to the URL held in that other field,
626
- // opened in a new tab. Only http(s) hrefs are linked — anything else (e.g. a
627
- // javascript: URL smuggled through row data) falls back to plain text — and
628
- // external links get rel=noopener noreferrer so the opened page can't reach
629
- // window.opener. Shared by the top-level grid and child grids.
624
+ // A grid td cell. Two column-declared linking modes, checked in order:
625
+ // 1. linkField — the cell text becomes a link to the URL held in that other
626
+ // field. Only http(s) hrefs are linked (a javascript:/other-scheme URL
627
+ // smuggled through row data falls back to plain text).
628
+ // 2. link: { kind: "processExplorer", keyField } — a structured, engine-aware
629
+ // link: the cell text links to the Nano console's explorer view for the
630
+ // process instance whose key is held in the row's keyField. The console
631
+ // path is constructed HERE (never taken from row data) and the key is
632
+ // URL-encoded, so row data can't smuggle a path or scheme; only a
633
+ // non-empty key (after trimming surrounding whitespace) produces a link.
634
+ // Unknown link kinds fall back to plain text so an unrecognised schema
635
+ // can't render a broken anchor.
636
+ // Both open in a new tab with rel=noopener noreferrer so the opened page can't
637
+ // reach window.opener. Shared by the top-level grid and child grids.
630
638
  function gridCell(col, row) {
631
639
  const text = row[col.field] == null ? "" : String(row[col.field]);
632
640
  if (col.linkField) {
@@ -639,6 +647,18 @@ function gridCell(col, row) {
639
647
  );
640
648
  }
641
649
  }
650
+ if (col.link && col.link.kind === "processExplorer" && col.link.keyField) {
651
+ const key = row[col.link.keyField];
652
+ const keyStr = key == null ? "" : String(key).trim();
653
+ if (text !== "" && keyStr !== "") {
654
+ const href = "/console/explorer?instance=" + encodeURIComponent(keyStr);
655
+ return el(
656
+ "td",
657
+ {},
658
+ el("a", { class: "pc-link", href, target: "_blank", rel: "noopener noreferrer" }, text),
659
+ );
660
+ }
661
+ }
642
662
  return el("td", {}, text);
643
663
  }
644
664