@nanobpm/urban 0.82.0 → 0.82.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/urban",
3
- "version": "0.82.0",
3
+ "version": "0.82.1",
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",
@@ -2769,7 +2769,11 @@ function renderAppView(node) {
2769
2769
  });
2770
2770
 
2771
2771
  const section = el("section", { class: "pc-appview" + (fill ? " pc-appview-fill" : "") });
2772
- if (title) section.append(el("div", { class: "pc-appview-title" }, title));
2772
+ // The title is an <h2> (the tag EVERY card renderer uses and makeCollapsible lifts into the
2773
+ // collapse header), not a <div>: a collapsible appView's title must be lifted out of the body,
2774
+ // otherwise it double-renders (header label + leftover in-body title). The .pc-appview-title CSS
2775
+ // is class-based, so a non-collapsible appView renders identically.
2776
+ if (title) section.append(el("h2", { class: "pc-appview-title" }, title));
2773
2777
  section.append(frame);
2774
2778
  return section;
2775
2779
  }
@@ -157,14 +157,29 @@ test("#416: renderAppView mounts a base-relative iframe honoring embed/title/fil
157
157
  assert.equal(frame.getAttribute("title"), "Agent networks");
158
158
  assert.equal(frame.className, "pc-appview-frame");
159
159
 
160
- // The title also renders as a visible label above the frame.
160
+ // The title also renders as a visible label above the frame — as an <h2> (see below).
161
161
  const label = section.children.find(
162
162
  (c): c is FakeElement => c instanceof FakeElement && c.className === "pc-appview-title",
163
163
  );
164
164
  assert.ok(label, "the title renders as a label");
165
+ assert.equal(label.tagName, "H2", "the appView title must be an <h2> so makeCollapsible lifts it (no duplicate title when collapsible)");
165
166
  assert.equal(label.textContent, "Agent networks");
166
167
  });
167
168
 
169
+ test("#471: the appView title is an <h2> so a collapsible appView doesn't double-render it", (t) => {
170
+ // makeCollapsible lifts a card's title out of the body into the collapse header by finding the
171
+ // renderer's <h2> (card.querySelector('h2')). Every card renderer emits an <h2>; renderAppView
172
+ // used to emit a <div class=pc-appview-title>, so the lift missed it and a collapsible appView
173
+ // showed its title twice (header label + leftover in-body div). Pin the tag so that can't regress.
174
+ t.after(installFakeDom());
175
+ const section = asFake(renderAppView({ type: "appView", props: { title: "Staged proposals", embed: "./e.html" } }));
176
+ const label = section.children.find(
177
+ (c): c is FakeElement => c instanceof FakeElement && c.className === "pc-appview-title",
178
+ );
179
+ assert.ok(label, "the appView renders a title label");
180
+ assert.equal(label.tagName, "H2", "renderAppView must emit its title as the <h2> makeCollapsible lifts");
181
+ });
182
+
168
183
  test("#416: renderAppView rebases a root-absolute embed onto the mount root", (t) => {
169
184
  t.after(installFakeDom());
170
185
  // A root-absolute "/embed.html" would escape the console proxy prefix and hit the
@@ -579,6 +579,16 @@ test("renderer supports an opt-in sticky nav bar", async () => {
579
579
  assert.match(css, /\.pc-bar\.pc-sticky \{[^}]*position:sticky[^}]*top:0/);
580
580
  });
581
581
 
582
+ test("#471: a fill appView keeps its height when also collapsible (flex propagates through .pc-card-body)", async () => {
583
+ const css = (await dispatch("GET", "/")).body ?? "";
584
+ // makeCollapsible reparents a collapsible node's body (the iframe, for an appView) into a
585
+ // `.pc-card-body`, so a `fill` appView's frame is no longer a direct flex child of
586
+ // `.pc-appview-fill` and its `flex:1` stops applying. The shell CSS must make the collapse body a
587
+ // grow-able flex column so `fill` propagates through it to the frame.
588
+ assert.match(css, /\.pc-appview-fill \.pc-appview-frame \{[^}]*flex:1 1 auto/);
589
+ assert.match(css, /\.pc-appview-fill \.pc-card-body \{[^}]*flex:1 1 auto[^}]*display:flex[^}]*flex-direction:column/);
590
+ });
591
+
582
592
  test("renderer exposes an embed-gated host-navigation bridge", async () => {
583
593
  const res = await dispatch("GET", "/app/runtime.js");
584
594
  const js = res.body ?? "";
@@ -905,6 +905,11 @@ table.pc-grid th { font-weight:600; color:var(--nano-text-muted); }
905
905
  .pc-appview-frame { display:block; width:100%; min-height:24rem; border:1px solid var(--nano-edge); border-radius:.6rem; background:var(--nano-inset); }
906
906
  .pc-appview-fill { display:flex; flex-direction:column; min-height:min(80vh,48rem); }
907
907
  .pc-appview-fill .pc-appview-frame { flex:1 1 auto; min-height:0; }
908
+ /* When a fill appView is ALSO collapsible, makeCollapsible reparents the iframe into a
909
+ .pc-card-body, so the frame is no longer a direct flex child of .pc-appview-fill and its
910
+ flex:1 above stops taking effect (the panel loses its height). Make the collapse body a flex
911
+ item that grows AND a flex column, so fill propagates through it to the frame. */
912
+ .pc-appview-fill .pc-card-body { flex:1 1 auto; min-height:0; display:flex; flex-direction:column; }
908
913
  /* A standalone button node + the modal it opens (e.g. a copy-pasteable prompt). */
909
914
  .pc-buttonrow { margin:1rem 0; }
910
915
  .pc-btn-ghost { background:transparent; color:var(--nano-text-muted); border:1px solid var(--nano-edge); }