pageship 0.6.0 → 0.6.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/dist/cli.mjs CHANGED
@@ -957,7 +957,21 @@ function isGitHubAccountId(value) {
957
957
  return typeof value === "string" && /^[1-9][0-9]{0,19}(?![\s\S])/u.test(value);
958
958
  }
959
959
  function validViewer(value) {
960
- return record(value) && isGitHubAccountId(value.githubAccountId) && isGitHubLogin(value.login) && (value.name === null || typeof value.name === "string") && typeof value.addedAt === "number" && Number.isSafeInteger(value.addedAt) && value.addedAt >= 0;
960
+ return record(value) && isGitHubAccountId(value.githubAccountId) && isGitHubLogin(value.login) && (value.name === null || typeof value.name === "string") && typeof value.addedAt === "number" && Number.isSafeInteger(value.addedAt) && value.addedAt >= 0 && typeof value.viewCount === "number" && Number.isSafeInteger(value.viewCount) && value.viewCount >= 0 && (value.lastViewedAt === null || typeof value.lastViewedAt === "number" && Number.isSafeInteger(value.lastViewedAt) && value.lastViewedAt >= 0);
961
+ }
962
+ function normalizeViewer(value) {
963
+ if (!record(value)) return;
964
+ const normalized = {
965
+ ...value,
966
+ ...value.viewCount === void 0 ? { viewCount: 0 } : {},
967
+ ...value.lastViewedAt === void 0 ? { lastViewedAt: null } : {}
968
+ };
969
+ return validViewer(normalized) ? normalized : void 0;
970
+ }
971
+ function normalizeViewers(value) {
972
+ if (!Array.isArray(value)) return;
973
+ const viewers = value.map(normalizeViewer);
974
+ return viewers.every((viewer) => viewer !== void 0) ? viewers : void 0;
961
975
  }
962
976
  function validUserMatch(value) {
963
977
  return record(value) && isGitHubAccountId(value.githubAccountId) && isGitHubLogin(value.login);
@@ -997,7 +1011,11 @@ async function getDocumentViewers(baseUrl, id, options = {}) {
997
1011
  path: { id }
998
1012
  });
999
1013
  if (!response?.ok) viewerFailed(response, error);
1000
- if (data?.status === "listed" && Array.isArray(data.viewers) && data.viewers.every(validViewer)) return data;
1014
+ const viewers = data?.status === "listed" ? normalizeViewers(data.viewers) : void 0;
1015
+ if (data && viewers) return {
1016
+ ...data,
1017
+ viewers
1018
+ };
1001
1019
  throw new Error("Invalid document viewer listing response.");
1002
1020
  }
1003
1021
  /**
@@ -1019,14 +1037,17 @@ async function updateDocumentSharing(baseUrl, id, change, options = {}) {
1019
1037
  body: change
1020
1038
  });
1021
1039
  if (!response?.ok) viewerFailed(response, error);
1022
- const document = data?.status === "sharing_updated" ? normalizeDocument(data.document) : void 0;
1023
- if (data && document && (change.visibility === void 0 || document.visibility === change.visibility) && [
1024
- data.viewers,
1025
- data.added,
1026
- data.removed
1027
- ].every((list) => Array.isArray(list) && list.every(validViewer))) return {
1028
- ...data,
1029
- document
1040
+ const updated = data?.status === "sharing_updated" ? data : void 0;
1041
+ const document = updated && normalizeDocument(updated.document);
1042
+ const viewers = updated && normalizeViewers(updated.viewers);
1043
+ const added = updated && normalizeViewers(updated.added);
1044
+ const removed = updated && normalizeViewers(updated.removed);
1045
+ if (updated && document && viewers && added && removed && (change.visibility === void 0 || document.visibility === change.visibility)) return {
1046
+ ...updated,
1047
+ document,
1048
+ viewers,
1049
+ added,
1050
+ removed
1030
1051
  };
1031
1052
  throw new Error("Invalid document sharing response.");
1032
1053
  }
@@ -1530,7 +1551,7 @@ function commandLine(command, args) {
1530
1551
  }
1531
1552
  //#endregion
1532
1553
  //#region package.json
1533
- var version = "0.6.0";
1554
+ var version = "0.6.1";
1534
1555
  //#endregion
1535
1556
  //#region src/publish.ts
1536
1557
  const execute = promisify(execFile);
@@ -3,7 +3,7 @@ name: pageship
3
3
  description: Publish an HTML file to Pageship and manage what is published. Use when the user asks to publish, share or get a link to a document, update its published copy, change its visibility or viewers, list, inspect history, download or restore published documents, or sign in to Pageship. To write, edit, rename or move a document, load pageship-docs instead.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.6.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Pageship
@@ -3,7 +3,7 @@ name: pageship-chart
3
3
  description: "Later step of pageship-docs: for a request to write or edit a document, load pageship-docs first; load this skill first only when the user names it. Pattern for charts in a Pageship HTML document, loaded by a pageship- kind skill before it draws a chart."
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.6.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Chart
@@ -3,7 +3,7 @@ name: pageship-code-block
3
3
  description: "Later step of pageship-docs: for a request to write or edit a document, load pageship-docs first; load this skill first only when the user names it. Pattern for code in a Pageship HTML document, such as source code, commands, configuration or logs, loaded by a pageship- kind skill before the first code block."
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.6.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Code block
@@ -3,7 +3,7 @@ name: pageship-decision-record
3
3
  description: HTML decision records, as the second step of pageship-docs. For any request to record, write, regenerate, edit or move a decision, load pageship-docs first; load this skill first only when the user names it.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.6.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Decision record
@@ -3,7 +3,7 @@ name: pageship-diagram
3
3
  description: "Later step of pageship-docs: for a request to write or edit a document, load pageship-docs first; load this skill first only when the user names it. Pattern for diagrams in a Pageship HTML document, such as a process, a timeline, a system map or states, loaded by a pageship- kind skill before it draws a diagram."
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.6.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Diagram
@@ -3,7 +3,7 @@ name: pageship-docs
3
3
  description: First step for any document people read, such as a report, plan, spec, explainer, review, decision record or slide deck, in HTML or Markdown. Load it before any other pageship- skill. Use when the user asks for one of these documents, asks to regenerate, redesign, edit, rename, move or copy an HTML source file, or asks to record whether they want HTML or Markdown.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.6.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Pageship documents
@@ -12,7 +12,7 @@ Run the linter, html-validate with the config and rules in this folder:
12
12
  npx --yes html-validate@11.17.0 --config <pageship-docs folder>/references/html-validate.json <file>
13
13
  ```
14
14
 
15
- It reports HTML errors, accessibility problems such as a missing `lang` or `alt` or a skipped heading level, and breaks of three rules: [one standalone file](file.md#one-standalone-file) and [trusted libraries only](file.md#trusted-libraries-only) in the file rules, and **Height** in [the design rules](design.md#base-rules-every-file-meets). Fix each problem it reports and run it again. Done when it reports nothing. When `npx` can't run, such as without Node or network, check the file against those three rules by reading it, and say in the hand-over that the linter didn't run.
15
+ It reports HTML errors, accessibility problems such as a missing `lang` or `alt` or a skipped heading level, and breaks of four rules: [one standalone file](file.md#one-standalone-file), [trusted libraries only](file.md#trusted-libraries-only) and [link previews](file.md#link-previews) in the file rules, and **Height** in [the design rules](design.md#base-rules-every-file-meets). Fix each problem it reports and run it again. Done when it reports nothing. When `npx` can't run, such as without Node or network, check the file against those four rules by reading it, and say in the hand-over that the linter didn't run.
16
16
 
17
17
  Then:
18
18
 
@@ -51,7 +51,7 @@ Then use every control by keyboard and confirm it works and shows focus. Done wh
51
51
 
52
52
  ## Claims
53
53
 
54
- Read the file sentence by sentence beside the source, captions, labels and tooltips included. Keep each claim the source states. A claim one step past it goes, or becomes an open question: a cause, when something starts or ends, a count, how far a rule reaches, or a new name for something the source names. A caption claims only what its figure draws. Done when you can point to the source's words behind every sentence.
54
+ Read the file sentence by sentence beside the source, captions, labels, tooltips and the link preview's description included. Keep each claim the source states. A claim one step past it goes, or becomes an open question: a cause, when something starts or ends, a count, how far a rule reaches, or a new name for something the source names. A caption claims only what its figure draws. Done when you can point to the source's words behind every sentence.
55
55
 
56
56
  ## The structure you added
57
57
 
@@ -32,6 +32,39 @@ Mermaid, only when a pattern skill calls for it:
32
32
 
33
33
  Hosted fonts from `https://fonts.googleapis.com/css2?...` through a `<link rel="stylesheet">`, which can't carry `integrity`.
34
34
 
35
+ ## Link previews
36
+
37
+ Chat apps and social sites show a card for a shared link, built from tags in the file's `<head>`. Every file carries them, after `<meta charset>`, the viewport and `<title>`:
38
+
39
+ ```html
40
+ <title>Q3 support report</title>
41
+ <meta
42
+ name="description"
43
+ content="Ticket volume rose 18% in Q3 while first-reply time held at 2 hours."
44
+ />
45
+ <meta property="og:type" content="article" />
46
+ <meta property="og:title" content="Q3 support report" />
47
+ <meta
48
+ property="og:description"
49
+ content="Ticket volume rose 18% in Q3 while first-reply time held at 2 hours."
50
+ />
51
+ <meta property="og:image" content="https://example.com/q3/tickets-by-week.png" />
52
+ <meta property="og:image:alt" content="Tickets by week, July to September" />
53
+ <meta property="og:image:width" content="1200" />
54
+ <meta property="og:image:height" content="630" />
55
+ <meta name="twitter:card" content="summary_large_image" />
56
+ <meta name="twitter:image:alt" content="Tickets by week, July to September" />
57
+ ```
58
+
59
+ - **Title.** `<title>` and `og:title` hold the same text: the document's title, as its `h1` gives it.
60
+ - **Description.** `description` and `og:description` hold the same one or two sentences, up to about 160 characters, that state the document's main point as its first lines do. It follows the [content rules](content.md) like any other text in the file.
61
+ - **Type.** `og:type` is `article`.
62
+ - **Image.** The card's image is the first of these that is a PNG, JPEG or GIF file at an `https://` URL: the image or chart that leads the page, then the first image in the page with alt text. Its `alt` becomes `og:image:alt` and `twitter:image:alt`. Its size in pixels, when you measured the image file itself, becomes `og:image:width` and `og:image:height`; a size the page displays it at is not that size, so without a measured size leave those two out. Sites can't show an inline SVG or a `data:` URL, so a page whose images and charts are only those leaves out every image tag.
63
+ - **Card.** `twitter:card` is `summary_large_image` with an image and `summary` without. X reads the title, description and image from the `og:` tags, so the only other `twitter:` tag is `twitter:image:alt`.
64
+ - **No address.** Leave out `og:url` and `<link rel="canonical">`: the file doesn't know where it will be published, and sites use the address they fetched.
65
+
66
+ An edit that changes the title, the main point or the image updates these tags to match.
67
+
35
68
  ## The Pageship sandbox
36
69
 
37
70
  A published file runs with scripts allowed and everything else blocked, so it works without these:
@@ -316,11 +316,105 @@ class NoViewportHeight extends Rule {
316
316
  }
317
317
  }
318
318
 
319
+ const entities = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: " " };
320
+
321
+ /** Text with its character references decoded and its runs of white space as single spaces. */
322
+ const plainText = (text) =>
323
+ text
324
+ .replace(/&(?:#(\d+)|#x([\da-f]+)|(\w+));/giu, (match, decimal, hex, name) =>
325
+ decimal || hex
326
+ ? String.fromCodePoint(Number.parseInt(decimal ?? hex, decimal ? 10 : 16))
327
+ : (entities[name.toLowerCase()] ?? match),
328
+ )
329
+ .replace(/\s+/gu, " ")
330
+ .trim();
331
+
332
+ /** The tags a link preview reads. Others, such as one `theme-color` per colour scheme, may repeat. */
333
+ const previewTags = /^(?:description|og:.+|twitter:.+)$/u;
334
+
335
+ /** An `https://` URL whose path doesn't end in `.svg`, which link previews don't show. */
336
+ const previewImage = /^https:\/\/(?![^?#]*\.svg(?:[?#]|$))/iu;
337
+
338
+ class LinkPreview extends Rule {
339
+ documentation() {
340
+ return {
341
+ description:
342
+ "Give the file the link preview tags in the file rules (references/file.md): a description, og:type, og:title and og:description matching the title and description, twitter:card, and an og:image only at an https:// URL to an image that isn't SVG, with og:image:alt and twitter:image:alt.",
343
+ };
344
+ }
345
+
346
+ setup() {
347
+ this.on("dom:ready", ({ document }) => {
348
+ const head = document.querySelector("head");
349
+ if (!head) return;
350
+ /** Each `<meta>` content by its `name` or `property`, with the element that has it. */
351
+ const metas = new Map();
352
+ for (const element of document.querySelectorAll("meta")) {
353
+ const key = attributeValue(element, "property") ?? attributeValue(element, "name");
354
+ if (!previewTags.test(key ?? "")) continue;
355
+ if (metas.has(key)) {
356
+ this.report(element, `The file has a second ${key} tag: keep one`);
357
+ continue;
358
+ }
359
+ metas.set(key, { element, content: plainText(attributeValue(element, "content") ?? "") });
360
+ }
361
+ const content = (key) => metas.get(key)?.content ?? "";
362
+ const at = (key) => metas.get(key)?.element ?? head;
363
+
364
+ for (const key of ["description", "og:type", "og:title", "og:description", "twitter:card"]) {
365
+ if (!content(key)) {
366
+ this.report(
367
+ at(key),
368
+ `The file needs the ${key} tag with content, as the file rules give`,
369
+ );
370
+ }
371
+ }
372
+ const title = document.querySelector("title");
373
+ if (title && content("og:title") && content("og:title") !== plainText(title.textContent)) {
374
+ this.report(at("og:title"), "og:title must hold the same text as <title>");
375
+ }
376
+ if (content("og:description") && content("og:description") !== content("description")) {
377
+ this.report(at("og:description"), "og:description must hold the same text as description");
378
+ }
379
+
380
+ const image = content("og:image");
381
+ if (metas.has("og:image") && !image) {
382
+ this.report(
383
+ at("og:image"),
384
+ "og:image is empty: give the image's URL, or leave out the image tags",
385
+ );
386
+ }
387
+ if (image && !previewImage.test(image)) {
388
+ this.report(
389
+ at("og:image"),
390
+ `og:image "${image}" is not an https:// URL to an image a link preview shows, such as a PNG or JPEG file: use one, or leave out the image tags`,
391
+ );
392
+ }
393
+ for (const key of ["og:image:alt", "twitter:image:alt"]) {
394
+ if (image && !content(key)) {
395
+ this.report(at("og:image"), `og:image needs ${key}, the image's alt text`);
396
+ }
397
+ }
398
+ if (!image && (metas.has("og:image:alt") || metas.has("twitter:image:alt"))) {
399
+ this.report(head, "The file has image alt tags but no og:image: leave them out too");
400
+ }
401
+ const card = image ? "summary_large_image" : "summary";
402
+ if (content("twitter:card") && content("twitter:card") !== card) {
403
+ this.report(
404
+ at("twitter:card"),
405
+ `twitter:card must be "${card}" for a file ${image ? "with" : "without"} og:image`,
406
+ );
407
+ }
408
+ });
409
+ }
410
+ }
411
+
319
412
  export default {
320
413
  name: "pageship",
321
414
  rules: {
322
415
  "pageship/trusted-libraries": TrustedLibraries,
323
416
  "pageship/no-relative-paths": NoRelativePaths,
324
417
  "pageship/no-viewport-height": NoViewportHeight,
418
+ "pageship/link-preview": LinkPreview,
325
419
  },
326
420
  };
@@ -8,6 +8,7 @@
8
8
  "heading-level": ["error", { "allowMultipleH1": false }],
9
9
  "pageship/trusted-libraries": "error",
10
10
  "pageship/no-relative-paths": "error",
11
- "pageship/no-viewport-height": "error"
11
+ "pageship/no-viewport-height": "error",
12
+ "pageship/link-preview": "error"
12
13
  }
13
14
  }
@@ -3,7 +3,7 @@ name: pageship-explainer
3
3
  description: HTML explainers, as the second step of pageship-docs. For any request to explain how something works, or to write, regenerate, edit or move an explainer, load pageship-docs first; load this skill first only when the user names it.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.6.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Explainer
@@ -3,7 +3,7 @@ name: pageship-interaction
3
3
  description: "Later step of pageship-docs: for a request to write or edit a document, load pageship-docs first; load this skill first only when the user names it. Pattern for controls in a Pageship HTML document, such as stepping through stages, tracing a path, tabs, filters, revealing detail and pointer states on charts and diagrams, loaded by a pageship- kind skill before it adds any control."
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.6.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Interaction
@@ -3,7 +3,7 @@ name: pageship-key-figures
3
3
  description: "Later step of pageship-docs: for a request to write or edit a document, load pageship-docs first; load this skill first only when the user names it. Pattern for the few numbers and callouts a Pageship HTML document opens with, loaded by a pageship- kind skill before it writes key figures."
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.6.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Key figures
@@ -3,7 +3,7 @@ name: pageship-media
3
3
  description: "Later step of pageship-docs: for a request to write or edit a document, load pageship-docs first; load this skill first only when the user names it. Pattern for images, video and audio in a Pageship HTML document, loaded by a pageship- kind skill before it shows media."
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.6.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Media
@@ -3,7 +3,7 @@ name: pageship-motion
3
3
  description: "Later step of pageship-docs: for a request to write or edit a document, load pageship-docs first; load this skill first only when the user names it. Pattern for animation and transitions in a Pageship HTML document, such as a chart that grows into view, a sequence that plays through a diagram, or a marker moving along a path, loaded by a pageship- kind skill before it draws a chart, a graphic or a sequence, or before anything else on the page moves."
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.6.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Motion
@@ -3,7 +3,7 @@ name: pageship-plan
3
3
  description: HTML plans, as the second step of pageship-docs. For any request to write, regenerate, edit or move a plan, load pageship-docs first; load this skill first only when the user names it.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.6.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Plan
@@ -3,7 +3,7 @@ name: pageship-report
3
3
  description: HTML reports, as the second step of pageship-docs. For any request to write, regenerate, edit or move a report, load pageship-docs first; load this skill first only when the user names it.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.6.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Report
@@ -3,7 +3,7 @@ name: pageship-review-report
3
3
  description: HTML review reports, as the second step of pageship-docs. For any request to review a change or a plan and write up the findings, or to write, regenerate, edit or move a review report, load pageship-docs first; load this skill first only when the user names it.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.6.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Review report
@@ -3,7 +3,7 @@ name: pageship-slide-deck
3
3
  description: HTML slide decks, as the second step of pageship-docs. For any request to make slides or a presentation, or to regenerate, edit or move a deck, load pageship-docs first; load this skill first only when the user names it.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.6.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Slide deck
@@ -3,7 +3,7 @@ name: pageship-spec
3
3
  description: HTML specs, as the second step of pageship-docs. For any request to write, regenerate, edit or move a spec, load pageship-docs first; load this skill first only when the user names it.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.6.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Spec
@@ -3,7 +3,7 @@ name: pageship-table
3
3
  description: "Later step of pageship-docs: for a request to write or edit a document, load pageship-docs first; load this skill first only when the user names it. Pattern for tables in a Pageship HTML document, loaded by a pageship- kind skill before it writes a table."
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.6.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Table
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pageship",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Pageship CLI for checking server API compatibility.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {