humanish 0.20.5 → 0.22.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.
@@ -102,11 +102,14 @@ e2b-URL / host-digest redaction discipline.
102
102
  ## Mobile fidelity caveat
103
103
 
104
104
  The example roster runs mobile-LAYOUT seats (`device: mobile` 414×896, `small-mobile` 360×740). On the
105
- E2B-desktop route ONLY width/height physically render — the X screen is sized to the preset so the
106
- app's width-based responsive CSS fires (real mobile LAYOUT), but there is NO touch input, and
107
- `isMobile`/DPR are prompt-signal + metadata (DPR renders only via the CDP geometry path). "3 mobile
108
- personas" = 3 mobile-LAYOUT desktop-Chromium seats, not touch devices. Do not over-read the results as
109
- true mobile-device coverage.
105
+ E2B-desktop route the rendered WIDTH is floored to `MIN_DESKTOP_RENDER_WIDTH` (500) because Chrome
106
+ refuses a narrower window and a narrower X screen clipped the page (0.20.3, #304), so both presets
107
+ render at 500 wide and are identical in layout; only HEIGHT renders as declared.
108
+ `desktopGeometry.screen.verified` compares the floored number with itself, so it does not attest the
109
+ preset width. There is NO touch input, and `isMobile`/DPR are prompt-signal + metadata (DPR renders
110
+ only via the CDP geometry path). "3 mobile personas" = 3 mobile-LAYOUT desktop-Chromium seats, not
111
+ touch devices. Do not over-read the results as true mobile-device coverage. True sub-500 rendering is
112
+ the #221 upgrade.
110
113
 
111
114
  ## Watch-from-phone
112
115
 
@@ -81,11 +81,17 @@ responses, but durable run bundles use the public-safe `[target-cwd]` marker.
81
81
 
82
82
  ## Hosted Desktop Geometry
83
83
 
84
- Hosted browser streams may carry additive `desktopGeometry` evidence. Its fields keep four
84
+ Hosted browser streams may carry additive `desktopGeometry` evidence. Its fields keep five
85
85
  different facts separate:
86
86
 
87
87
  - `screen.requested`: the E2B/X screen size requested by config;
88
88
  - `screen.verified`: the screen size measured in-sandbox with `xdpyinfo`;
89
+ - `screen.declared`: the device preset the lab ASKED for, present only when it differs from
90
+ `screen.requested` because the rendered width was floored to Chrome's ~500px window minimum.
91
+ `verified` compares the floored number with itself and reports a match, so a matching
92
+ `verified` block is NOT evidence that the preset width rendered. When `declared` is present,
93
+ it did not: a `mobile` (414) and a `small-mobile` (360) seat both render at 500 and are
94
+ indistinguishable by rendered width;
89
95
  - `browserWindow`: outer browser bounds measured after the window-fill attempt;
90
96
  - `viewport`: the page's CSS layout viewport and device-pixel ratio measured through CDP on
91
97
  Chromium-family hosted browsers.
@@ -3,7 +3,7 @@
3
3
  Date: 2026-06-02 (current-state note updated 2026-07-14)
4
4
 
5
5
  Status: reference map for the major contracts shipped through source version
6
- `0.20.5`; it is not an exhaustive inventory of command/result envelopes. Exported types,
6
+ `0.22.0`; it is not an exhaustive inventory of command/result envelopes. Exported types,
7
7
  schema constants, parsers, and validators in `src/` are authoritative. Rows
8
8
  marked "reserved" name layering intent only — no code emits or validates them
9
9
  yet. Do not emit a reserved schema.
@@ -16,7 +16,7 @@ Humanish should be the open-source CLI that lets a maintainer ask:
16
16
  The answer should be observable, verifiable, public-safe, and easy to turn into
17
17
  actionable feedback.
18
18
 
19
- ## Current Program Truth (source `0.20.5`)
19
+ ## Current Program Truth (source `0.22.0`)
20
20
 
21
21
  The package source and repository implementation in this tree agree on these
22
22
  points:
@@ -18,11 +18,20 @@ a public deployment YOU own/operate.
18
18
  ## Mobile fidelity caveat (read before over-reading the results)
19
19
 
20
20
  The seats are `device: mobile` (414×896) and `device: small-mobile` (360×740). On the E2B-desktop
21
- route ONLY width/height physically render — the X screen is sized to the preset so the app's
22
- width-based responsive CSS fires (real mobile LAYOUT), but there is NO touch input, and isMobile/DPR
23
- are prompt-signal + metadata (DPR renders only via the CDP geometry path). So "3 mobile personas"
24
- means 3 mobile-LAYOUT desktop-Chromium seats, NOT touch devices. If a flow needs true touch/tap
25
- semantics, these seats will not exercise it.
21
+ route the rendered WIDTH is floored to `MIN_DESKTOP_RENDER_WIDTH` (500): Chrome refuses a window
22
+ narrower than ~500 CSS px, and a narrower X screen let the window overflow and clip the page
23
+ (0.20.3, #304). So both presets render at **500 wide**, `mobile` and `small-mobile` are identical in
24
+ layout on this route, and this example does NOT currently exercise two different mobile widths. Only
25
+ the HEIGHT renders as declared (896 and 740).
26
+
27
+ Note that `desktopGeometry.screen.verified` checks the FLOORED number against itself, so a matching
28
+ `verified` block is not evidence that the preset width rendered. Read `requested` against the preset
29
+ in `device-presets.ts` if you need to know.
30
+
31
+ There is NO touch input, and isMobile/DPR are prompt-signal + metadata (DPR renders only via the CDP
32
+ geometry path). So "3 mobile personas" means 3 mobile-LAYOUT desktop-Chromium seats, NOT touch
33
+ devices. If a flow needs true touch/tap semantics, these seats will not exercise it. True sub-500
34
+ CSS-viewport rendering is the #221 CDP-device-emulation upgrade.
26
35
 
27
36
  ## Run it
28
37
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Status: public-safe contributor and agent ramp.
4
4
 
5
- Package/source version in this tree: `0.20.5` (2026-08-03). The containment boundary introduced in
5
+ Package/source version in this tree: `0.22.0` (2026-08-04). The containment boundary introduced in
6
6
  `0.15.1` remains in force: managed run and output paths bind to validated
7
7
  physical filesystem identities, and stored provider IDs are evidence, not
8
8
  cleanup authority. The bundled OSS meta-lab is dry-run only until
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "humanish",
3
- "version": "0.20.5",
3
+ "version": "0.22.0",
4
4
  "description": "Open-source-safe CLI for persona simulation, observer review, and public-safe feedback drafts.",
5
5
  "author": "Daniel G Wilson <daniel@danielgwilson.com>",
6
6
  "keywords": [
@@ -1,30 +0,0 @@
1
- import type { CommsChannel } from "./comms-types.js";
2
- /** A Resend send payload we accepted, normalized (runtime-only; for inspection/tests). */
3
- export interface ResendEmailPayload {
4
- from: string;
5
- to: string[];
6
- subject?: string;
7
- html?: string;
8
- text?: string;
9
- }
10
- export interface ResendCatchServer {
11
- /** Point the app here: `RESEND_BASE_URL=<url>` (loopback only). */
12
- readonly url: string;
13
- readonly port: number;
14
- /** Every payload accepted this run (runtime-only). */
15
- readonly received: ResendEmailPayload[];
16
- close(): Promise<void>;
17
- }
18
- export interface ResendCatchOptions {
19
- /** Bind host. Default 127.0.0.1 (loopback ONLY — never bind a public interface). */
20
- host?: string;
21
- /** Port. Default 0 (ephemeral — read the chosen port off the returned server). */
22
- port?: number;
23
- /** Deterministic id for the Resend-shaped response (tests). Default a zero-padded counter. */
24
- idFor?: (n: number) => string;
25
- }
26
- /**
27
- * Start the Resend-shaped catch server, routing accepted sends into `channel`. Returns the loopback
28
- * URL to hand the app as `RESEND_BASE_URL`. `close()` in a finally (mirror by-id teardown).
29
- */
30
- export declare function startResendCatchServer(channel: CommsChannel, options?: ResendCatchOptions): Promise<ResendCatchServer>;
@@ -1,120 +0,0 @@
1
- // A LOOPBACK catch server that speaks the Resend `POST /emails` wire shape, so an app hardwired to
2
- // Resend is redirected into the faux bus with ONE env var and no code change: point the app at this
3
- // server via `RESEND_BASE_URL=<url>` (the official `resend` Node SDK reads that env var / a `baseUrl`
4
- // option). The app's real verification email POSTs here instead of api.resend.com; we route it into
5
- // the CommsChannel and the persona reads it. Nothing is delivered onward. (#297 — the API-first
6
- // analog of pointing SMTP at a local catcher, since Resend is HTTPS-first and its own SMTP delivers
7
- // for real.) 127.0.0.1 only; bodies are never logged.
8
- import { createServer } from "node:http";
9
- /** "Name <email@host>" → "email@host"; a bare address passes through. */
10
- function bareEmail(value) {
11
- const angle = value.match(/<([^>]+)>/);
12
- return (angle ? angle[1] ?? value : value).trim();
13
- }
14
- function asStringArray(value) {
15
- if (Array.isArray(value))
16
- return value.map((entry) => String(entry));
17
- if (typeof value === "string")
18
- return [value];
19
- return [];
20
- }
21
- function normalizeResendPayload(payload) {
22
- return {
23
- from: typeof payload.from === "string" ? payload.from : "",
24
- to: asStringArray(payload.to).map(bareEmail),
25
- ...(typeof payload.subject === "string" ? { subject: payload.subject } : {}),
26
- ...(typeof payload.html === "string" ? { html: payload.html } : {}),
27
- ...(typeof payload.text === "string" ? { text: payload.text } : {})
28
- };
29
- }
30
- function readBody(req, limit) {
31
- return new Promise((resolve, reject) => {
32
- let size = 0;
33
- const chunks = [];
34
- req.on("data", (chunk) => {
35
- size += chunk.length;
36
- if (size > limit) {
37
- req.destroy();
38
- reject(new Error("request body too large"));
39
- return;
40
- }
41
- chunks.push(chunk);
42
- });
43
- req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
44
- req.on("error", reject);
45
- });
46
- }
47
- const MAX_BODY_BYTES = 5 * 1024 * 1024;
48
- /**
49
- * Start the Resend-shaped catch server, routing accepted sends into `channel`. Returns the loopback
50
- * URL to hand the app as `RESEND_BASE_URL`. `close()` in a finally (mirror by-id teardown).
51
- */
52
- export async function startResendCatchServer(channel, options = {}) {
53
- const host = options.host ?? "127.0.0.1";
54
- const received = [];
55
- let idCounter = 0;
56
- const idFor = options.idFor ?? ((n) => `humanish-catch-${n.toString().padStart(6, "0")}`);
57
- const respondJson = (res, status, value) => {
58
- res.statusCode = status;
59
- res.setHeader("content-type", "application/json");
60
- res.end(JSON.stringify(value));
61
- };
62
- const handle = async (req, res) => {
63
- const path = (req.url ?? "/").split("?")[0];
64
- if (req.method === "GET" && (path === "/" || path === "/health")) {
65
- respondJson(res, 200, { ok: true, service: "humanish-resend-catch", channel: channel.channel });
66
- return;
67
- }
68
- // Resend's send endpoints: POST /emails (single) and /emails/batch. Batch is an array of sends.
69
- if (req.method === "POST" && (path === "/emails" || path === "/emails/batch")) {
70
- const raw = await readBody(req, MAX_BODY_BYTES);
71
- let parsed;
72
- try {
73
- parsed = JSON.parse(raw.length > 0 ? raw : "{}");
74
- }
75
- catch {
76
- respondJson(res, 422, { error: "invalid JSON body" });
77
- return;
78
- }
79
- const items = Array.isArray(parsed) ? parsed : [parsed];
80
- const ids = [];
81
- for (const item of items) {
82
- if (typeof item !== "object" || item === null)
83
- continue;
84
- const payload = normalizeResendPayload(item);
85
- received.push(payload);
86
- const inbound = {
87
- from: payload.from,
88
- to: payload.to,
89
- ...(payload.subject === undefined ? {} : { subject: payload.subject }),
90
- body: payload.html ?? payload.text ?? ""
91
- };
92
- await channel.deliverRaw(inbound);
93
- idCounter += 1;
94
- ids.push(idFor(idCounter));
95
- }
96
- // Resend-shaped response: a single send returns { id }; a batch returns { data: [{ id }, …] }.
97
- if (path === "/emails/batch")
98
- respondJson(res, 200, { data: ids.map((id) => ({ id })) });
99
- else
100
- respondJson(res, 200, { id: ids[0] ?? idFor(idCounter) });
101
- return;
102
- }
103
- respondJson(res, 404, { error: "not found" });
104
- };
105
- const server = createServer((req, res) => {
106
- void handle(req, res).catch(() => {
107
- if (!res.headersSent)
108
- respondJson(res, 500, { error: "catch server error" });
109
- });
110
- });
111
- await new Promise((resolve) => server.listen(options.port ?? 0, host, () => resolve()));
112
- const port = server.address().port;
113
- return {
114
- url: `http://${host}:${port}`,
115
- port,
116
- received,
117
- close: () => new Promise((resolve) => server.close(() => resolve()))
118
- };
119
- }
120
- //# sourceMappingURL=comms-resend-catch.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"comms-resend-catch.js","sourceRoot":"","sources":["../src/comms-resend-catch.ts"],"names":[],"mappings":"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"}