@olomonhelpdesk2/helpdesk 0.1.13 → 0.1.14

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.
@@ -106,7 +106,12 @@ function readExampleRoutes(raw) {
106
106
  }
107
107
  paths.set(route, path5);
108
108
  }
109
- return { ok: true, examples: { paths, refused } };
109
+ const again = /* @__PURE__ */ new Set();
110
+ for (const route of Array.isArray(body["again"]) ? body["again"] : []) {
111
+ if (typeof route === "string" && paths.has(route)) again.add(route);
112
+ else refused.push({ route: String(route), because: "it is named as safe to open again, but not as a route with a record" });
113
+ }
114
+ return { ok: true, examples: { paths, refused, again } };
110
115
  }
111
116
  function resolveVisit(route, examples) {
112
117
  const needs = dynamicSegments(route);
@@ -1298,7 +1303,9 @@ async function phoneFit(page, width) {
1298
1303
  if (content > laid + 1) return { fits: false, because: `it runs ${Math.round(content - laid)}px off the side` };
1299
1304
  const main2 = document.querySelector("main, [role=main]");
1300
1305
  const column = main2 ? main2.getBoundingClientRect().width : laid;
1301
- return column >= laid * 0.75 ? { fits: true } : { fits: false, because: `its main column is ${Math.round(column)}px of ${laid}px, beside the rest of the layout` };
1306
+ if (column < laid * 0.75) return { fits: false, because: `its main column is ${Math.round(column)}px of ${laid}px, beside the rest of the layout` };
1307
+ const overflow = main2 ? main2.scrollWidth - main2.clientWidth : 0;
1308
+ return overflow > 1 ? { fits: false, because: `its content runs ${Math.round(overflow)}px past its main column` } : { fits: true };
1302
1309
  },
1303
1310
  { width }
1304
1311
  )
@@ -1336,6 +1343,10 @@ import { createHash } from "node:crypto";
1336
1343
  var LAYOUT_MANIFEST_SCHEMA = "helpdesk.layout-manifest/1";
1337
1344
  var PHONE_SUFFIX = "--phone";
1338
1345
  var PHONE_VIEWPORT = { width: 390, height: 844 };
1346
+ var PHONE_VIEWPORTS = [PHONE_VIEWPORT, { width: 430, height: 932 }];
1347
+ function phoneViewport(width) {
1348
+ return PHONE_VIEWPORTS.find((viewport) => viewport.width === width) ?? PHONE_VIEWPORT;
1349
+ }
1339
1350
  var CONTROL_ROLES = [
1340
1351
  "button",
1341
1352
  "link",
@@ -2584,7 +2595,7 @@ async function runCapture(options) {
2584
2595
  const evidence = [];
2585
2596
  const build = await options.browser.build?.().catch(() => void 0);
2586
2597
  for (const [index, shot] of options.shots.entries()) {
2587
- const taken = await captureOne(options, shot, build);
2598
+ const taken = shot.variant === "phone" ? await captureAtPhoneWidths(options, shot, build) : await captureOne(options, shot, build);
2588
2599
  const outcome = shot.variant ? { ...taken.outcome, variant: shot.variant } : taken.outcome;
2589
2600
  outcomes.push(outcome);
2590
2601
  if (taken.observed && !shot.variant) survey.push(taken.observed);
@@ -2632,6 +2643,19 @@ async function layoutOf(page, shot, options, cover, size, landed) {
2632
2643
  return void 0;
2633
2644
  }
2634
2645
  }
2646
+ async function captureAtPhoneWidths(options, shot, build) {
2647
+ const tried = [];
2648
+ let taken;
2649
+ for (const { width } of PHONE_VIEWPORTS) {
2650
+ taken = await captureOne(options, { ...shot, phoneWidth: width }, build);
2651
+ if (taken.unfit === void 0) return taken;
2652
+ tried.push(`at ${width}px ${taken.unfit}`);
2653
+ }
2654
+ return {
2655
+ ...taken,
2656
+ outcome: { ...taken.outcome, reason: `not laid out for a phone (${tried.join("; ")}), so there is no phone-width frame` }
2657
+ };
2658
+ }
2635
2659
  async function captureOne(options, shot, build) {
2636
2660
  if (shot.unvisitable) {
2637
2661
  return { outcome: { shotId: shot.id, route: shot.route, status: "skipped", reason: shot.unvisitable } };
@@ -2639,7 +2663,7 @@ async function captureOne(options, shot, build) {
2639
2663
  const visit = shot.visit ?? shot.route;
2640
2664
  let page;
2641
2665
  try {
2642
- page = await options.browser.page(shot.variant ? { variant: shot.variant } : void 0);
2666
+ page = await options.browser.page(shot.variant ? { variant: shot.variant, ...shot.phoneWidth ? { width: shot.phoneWidth } : {} } : void 0);
2643
2667
  await page.goto(new URL(visit, options.baseUrl).toString());
2644
2668
  if (await page.isSignIn()) {
2645
2669
  return {
@@ -2700,9 +2724,10 @@ async function captureOne(options, shot, build) {
2700
2724
  route: shot.route,
2701
2725
  status: "skipped",
2702
2726
  result: observed.result,
2703
- reason: `not laid out for a phone at ${PHONE_VIEWPORT.width}px (${fit.because}), so there is no phone-width frame`
2727
+ reason: `not laid out for a phone at ${phoneViewport(shot.phoneWidth).width}px (${fit.because}), so there is no phone-width frame`
2704
2728
  },
2705
- observed
2729
+ observed,
2730
+ unfit: fit.because
2706
2731
  };
2707
2732
  }
2708
2733
  const evidence = shot.variant ? void 0 : await readEvidence(settledPage, shot.route, options, build, visit);
@@ -2769,7 +2794,7 @@ async function captureOne(options, shot, build) {
2769
2794
  });
2770
2795
  const image = frame.value;
2771
2796
  const size = pngSize(image);
2772
- const minimum = shot.variant === "phone" ? PHONE_VIEWPORT.width * CAPTURE_CONTRACT.stillScale : CAPTURE_CONTRACT.minStillWidth;
2797
+ const minimum = shot.variant === "phone" ? phoneViewport(shot.phoneWidth).width * CAPTURE_CONTRACT.stillScale : CAPTURE_CONTRACT.minStillWidth;
2773
2798
  if (size && size.width < minimum) {
2774
2799
  return {
2775
2800
  outcome: {
@@ -2988,8 +3013,9 @@ async function playwrightBrowser(options = {}) {
2988
3013
  };
2989
3014
  return {
2990
3015
  async page(wanted) {
3016
+ const phone = phoneViewport(wanted?.width);
2991
3017
  const context = await browser.newContext(
2992
- wanted?.variant === "phone" ? { ...contextOptions, viewport: { ...PHONE_VIEWPORT }, isMobile: true, hasTouch: true } : wanted?.variant === "clip" ? { ...contextOptions, viewport: { ...CLIP_VIEWPORT }, deviceScaleFactor: CAPTURE_CONTRACT.clipScale } : contextOptions
3018
+ wanted?.variant === "phone" ? { ...contextOptions, viewport: { ...phone }, isMobile: true, hasTouch: true } : wanted?.variant === "clip" ? { ...contextOptions, viewport: { ...CLIP_VIEWPORT }, deviceScaleFactor: CAPTURE_CONTRACT.clipScale } : contextOptions
2993
3019
  );
2994
3020
  if (!options.liveClock) {
2995
3021
  await context.addInitScript(freezeScript(CAPTURE_CONTRACT.pinnedClock));
@@ -3068,7 +3094,7 @@ async function playwrightBrowser(options = {}) {
3068
3094
  return readPageLayout(page, cover ?? []);
3069
3095
  },
3070
3096
  async phoneFit() {
3071
- return phoneFit(page, PHONE_VIEWPORT.width).catch(() => ({ fits: false, because: "the page could not be measured at that width" }));
3097
+ return phoneFit(page, phone.width).catch(() => ({ fits: false, because: "the page could not be measured at that width" }));
3072
3098
  },
3073
3099
  async location() {
3074
3100
  const url = new URL(page.url());
@@ -4669,11 +4695,18 @@ function shotsFromRepo(repo, previous) {
4669
4695
  }))
4670
4696
  );
4671
4697
  }
4672
- function withPhoneShots(shots, previous) {
4698
+ function withPhoneShots(shots, previous, again = /* @__PURE__ */ new Set()) {
4673
4699
  const planned = new Set(shots.map((shot) => shot.id));
4674
- const phones = shots.filter((shot) => !shot.visit && !shot.unvisitable && !shot.variant && !planned.has(`${shot.id}${PHONE_SUFFIX}`)).map((shot) => {
4700
+ const phones = shots.filter((shot) => (!shot.visit || again.has(shot.route)) && !shot.unvisitable && !shot.variant && !planned.has(`${shot.id}${PHONE_SUFFIX}`)).map((shot) => {
4675
4701
  const id = `${shot.id}${PHONE_SUFFIX}`;
4676
- return { id, route: shot.route, depicts: `${shot.depicts}, at phone width`, variant: "phone", ...seenBefore(previous.get(id)) };
4702
+ return {
4703
+ id,
4704
+ route: shot.route,
4705
+ ...shot.visit ? { visit: shot.visit } : {},
4706
+ depicts: `${shot.depicts}, at phone width`,
4707
+ variant: "phone",
4708
+ ...seenBefore(previous.get(id))
4709
+ };
4677
4710
  });
4678
4711
  return [...shots, ...phones];
4679
4712
  }
@@ -4943,7 +4976,7 @@ Approve one on the plan page, then run this again.`
4943
4976
  }
4944
4977
  }
4945
4978
  const withRecords = seeded?.examples ? withExamples(shots, seeded.examples) : shots;
4946
- const planned = seededLast(parsed.phone ? withPhoneShots(withRecords, previous) : withRecords);
4979
+ const planned = seededLast(parsed.phone ? withPhoneShots(withRecords, previous, seeded?.examples?.again) : withRecords);
4947
4980
  if (parsed.cassette) {
4948
4981
  try {
4949
4982
  stage = await stageAgainstRecording({
@@ -4993,7 +5026,7 @@ Approve one on the plan page, then run this again.`
4993
5026
  console.log(`${coverageReport(found, shots.length).join("\n")}
4994
5027
  `);
4995
5028
  }
4996
- const phones = parsed.phone ? `, each opened at its own address again at ${PHONE_VIEWPORT.width}px wide if it is laid out for a phone` : "";
5029
+ const phones = parsed.phone ? `, each opened at its own address again at phone width (${PHONE_VIEWPORTS.map((viewport) => `${viewport.width}px`).join(", then ")}) if it is laid out for a phone` : "";
4997
5030
  console.log(
4998
5031
  stage ? `Photographing ${shots.length} screen(s) of ${baseUrl}${phones}, served from ${parsed.cassette}.
4999
5032
  ` : `Photographing ${shots.length} screen(s) of ${baseUrl}${phones}.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olomonhelpdesk2/helpdesk",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "Reads a product's screens, plans its help articles, and photographs them.",
5
5
  "type": "module",
6
6
  "engines": {