@firedrill-tools/notion 0.1.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.
Files changed (65) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +402 -0
  3. package/firedrill/agent.target.json +17 -0
  4. package/firedrill/baseline.scenario.json +5 -0
  5. package/firedrill/bounded.scenario.json +19 -0
  6. package/firedrill/conformance.suite.json +23 -0
  7. package/firedrill/notion-bounded.drill.json +318 -0
  8. package/firedrill/notion-byte-budget.drill.json +116 -0
  9. package/firedrill/notion-mcp-aliases.drill.json +150 -0
  10. package/firedrill/notion-page-authoring.drill.json +254 -0
  11. package/firedrill/notion-rate-limited.drill.json +118 -0
  12. package/firedrill/notion-schema-growth.drill.json +88 -0
  13. package/firedrill/notion-scope-agent-only.drill.json +131 -0
  14. package/firedrill/notion-scope-auditor.drill.json +86 -0
  15. package/firedrill/notion-scope-board-bot.drill.json +128 -0
  16. package/firedrill/notion-scope-notes-bot.drill.json +303 -0
  17. package/firedrill/notion-scope-stranger.drill.json +773 -0
  18. package/firedrill/notion-task-triage.drill.json +277 -0
  19. package/firedrill/notion-trash-and-restore.drill.json +186 -0
  20. package/firedrill/notion-update-lost.drill.json +88 -0
  21. package/firedrill/notion-workspace-read.drill.json +258 -0
  22. package/firedrill/notion-write-unavailable.drill.json +161 -0
  23. package/firedrill/rate-limited.scenario.json +11 -0
  24. package/firedrill/tools/notion/app/assets/ATTRIBUTION.md +35 -0
  25. package/firedrill/tools/notion/app/assets/fonts/OFL.txt +93 -0
  26. package/firedrill/tools/notion/app/assets/fonts/inter-latin.woff2 +0 -0
  27. package/firedrill/tools/notion/app/assets/notion-wordmark.svg +1 -0
  28. package/firedrill/tools/notion/app/assets/notion.svg +1 -0
  29. package/firedrill/tools/notion/app/site/app.js +797 -0
  30. package/firedrill/tools/notion/app/site/assets/fonts/inter-latin.woff2 +0 -0
  31. package/firedrill/tools/notion/app/site/assets/notion-wordmark.svg +1 -0
  32. package/firedrill/tools/notion/app/site/assets/notion.svg +1 -0
  33. package/firedrill/tools/notion/app/site/chrome.js +104 -0
  34. package/firedrill/tools/notion/app/site/cover-picker.js +83 -0
  35. package/firedrill/tools/notion/app/site/database.js +648 -0
  36. package/firedrill/tools/notion/app/site/editors.js +320 -0
  37. package/firedrill/tools/notion/app/site/format-bar.js +97 -0
  38. package/firedrill/tools/notion/app/site/icons.js +131 -0
  39. package/firedrill/tools/notion/app/site/index.html +125 -0
  40. package/firedrill/tools/notion/app/site/page.js +826 -0
  41. package/firedrill/tools/notion/app/site/rich.js +159 -0
  42. package/firedrill/tools/notion/app/site/state.js +170 -0
  43. package/firedrill/tools/notion/app/site/styles.css +826 -0
  44. package/firedrill/tools/notion/app/site/ui.js +418 -0
  45. package/firedrill/tools/notion/behavior.mjs +1123 -0
  46. package/firedrill/tools/notion/lib/blocks.mjs +371 -0
  47. package/firedrill/tools/notion/lib/identity.mjs +123 -0
  48. package/firedrill/tools/notion/lib/ids.mjs +63 -0
  49. package/firedrill/tools/notion/lib/json-depth.mjs +26 -0
  50. package/firedrill/tools/notion/lib/markdown.mjs +381 -0
  51. package/firedrill/tools/notion/lib/properties.mjs +513 -0
  52. package/firedrill/tools/notion/lib/query.mjs +272 -0
  53. package/firedrill/tools/notion/lib/render.mjs +137 -0
  54. package/firedrill/tools/notion/lib/rich-text.mjs +134 -0
  55. package/firedrill/tools/notion/lib/size.mjs +44 -0
  56. package/firedrill/tools/notion/lib/state.mjs +192 -0
  57. package/firedrill/tools/notion/lib/wire.mjs +89 -0
  58. package/firedrill/tools/notion/notion.tool.json +9837 -0
  59. package/firedrill/update-lost.scenario.json +11 -0
  60. package/firedrill/world.json +7039 -0
  61. package/firedrill/write-unavailable.scenario.json +11 -0
  62. package/firedrill.json +5 -0
  63. package/package.json +63 -0
  64. package/starter.json +6482 -0
  65. package/test/conformance.mjs +1186 -0
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 178"><path fill="#fff" d="M10.691 7.666L109.012.404c12.077-1.037 15.181-.338 22.774 5.183l31.386 22.11c5.177 3.803 6.9 4.84 6.9 8.98v121.26c0 7.6-2.76 12.095-12.42 12.782l-114.177 6.912c-7.25.344-10.703-.693-14.5-5.532L5.864 142.046C1.718 136.514 0 132.374 0 127.535V19.748C0 13.535 2.76 8.353 10.691 7.666"/><path d="M109.012.404L10.691 7.666C2.761 8.353 0 13.536 0 19.748v107.787c0 4.839 1.718 8.979 5.864 14.511L28.975 172.1c3.797 4.84 7.25 5.876 14.5 5.532l114.177-6.912c9.654-.687 12.42-5.182 12.42-12.782V36.677c0-3.928-1.551-5.059-6.118-8.411l-.788-.569l-31.38-22.11C124.193.067 121.09-.633 109.012.404M46.057 34.692c-9.323.628-11.437.77-16.732-3.536L15.862 20.447c-1.368-1.386-.681-3.115 2.766-3.459l94.519-6.906c7.936-.693 12.07 2.073 15.174 4.49l16.212 11.745c.693.35 2.416 2.417.343 2.417l-97.61 5.875zM35.188 156.901V53.96c0-4.496 1.38-6.57 5.515-6.919l112.11-6.562c3.802-.344 5.52 2.073 5.52 6.562v102.255c0 4.495-.693 8.298-6.9 8.641l-107.283 6.22c-6.207.343-8.962-1.724-8.962-7.256M141.091 59.48c.687 3.11 0 6.219-3.11 6.574l-5.17 1.025v76.004c-4.49 2.416-8.624 3.796-12.077 3.796c-5.52 0-6.9-1.73-11.035-6.906l-33.814-53.2v51.47l10.697 2.423s0 6.22-8.63 6.22l-23.792 1.38c-.693-1.387 0-4.84 2.41-5.527l6.214-1.724V72.96l-8.624-.698c-.693-3.11 1.03-7.6 5.863-7.95l25.528-1.717l35.183 53.887V68.808l-8.967-1.03c-.693-3.809 2.067-6.575 5.514-6.912zm104.322 66.39V79.02h.811l33.785 46.85h10.637V57.022h-11.828v46.803h-.811l-33.785-46.803h-10.679v68.842h11.876zm78.663 1.055c15.607 0 25.101-10.212 25.101-27.151c0-16.893-9.542-27.151-25.101-27.151c-15.507 0-25.096 10.306-25.096 27.15c0 16.94 9.447 27.152 25.096 27.152m0-9.927c-8.251 0-12.972-6.296-12.972-17.224c0-10.88 4.721-17.224 12.972-17.224c8.209 0 12.93 6.343 12.93 17.224c0 10.928-4.674 17.224-12.93 17.224m35.91-56.109v13.12h-8.25v9.447h8.25v28.484c0 10.116 4.775 14.173 16.75 14.173c2.287 0 4.485-.237 6.202-.574v-9.258c-1.433.142-2.34.237-4.01.237c-4.957 0-7.155-2.286-7.155-7.44V83.457h11.165V74.01h-11.165V60.883h-11.786zm30.042 64.981h11.787V73.671h-11.787zm5.87-60.829c3.909 0 7.06-3.157 7.06-7.113c0-3.963-3.145-7.161-7.06-7.161c-3.862 0-7.06 3.198-7.06 7.16c0 3.957 3.198 7.108 7.06 7.108zm38.25 61.884c15.601 0 25.096-10.212 25.096-27.151c0-16.893-9.542-27.151-25.096-27.151c-15.512 0-25.101 10.306-25.101 27.15c0 16.94 9.447 27.152 25.101 27.152m0-9.927c-8.256 0-12.983-6.296-12.983-17.224c0-10.88 4.727-17.224 12.983-17.224c8.204 0 12.93 6.343 12.93 17.224c0 10.928-4.679 17.224-12.93 17.224m32.138 8.872h11.835V95.48c0-7.683 4.442-12.551 11.496-12.551c7.208 0 10.543 4.01 10.543 11.976v30.965H512V92.092c0-12.456-6.35-19.47-17.988-19.47c-7.783 0-13.03 3.578-15.512 9.4h-.812v-8.35h-11.402z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 268"><path fill="#fff" d="M16.092 11.538L164.09.608c18.179-1.56 22.85-.508 34.28 7.801l47.243 33.282C253.406 47.414 256 48.975 256 55.207v182.527c0 11.439-4.155 18.205-18.696 19.24L65.44 267.378c-10.913.517-16.11-1.043-21.825-8.327L8.826 213.814C2.586 205.487 0 199.254 0 191.97V29.726c0-9.352 4.155-17.153 16.092-18.188"/><path d="M164.09.608L16.092 11.538C4.155 12.573 0 20.374 0 29.726v162.245c0 7.284 2.585 13.516 8.826 21.843l34.789 45.237c5.715 7.284 10.912 8.844 21.825 8.327l171.864-10.404c14.532-1.035 18.696-7.801 18.696-19.24V55.207c0-5.911-2.336-7.614-9.21-12.66l-1.185-.856L198.37 8.409C186.94.1 182.27-.952 164.09.608M69.327 52.22c-14.033.945-17.216 1.159-25.186-5.323L23.876 30.778c-2.06-2.086-1.026-4.69 4.163-5.207l142.274-10.395c11.947-1.043 18.17 3.12 22.842 6.758l24.401 17.68c1.043.525 3.638 3.637.517 3.637L71.146 52.095zm-16.36 183.954V81.222c0-6.767 2.077-9.887 8.3-10.413L230.02 60.93c5.724-.517 8.31 3.12 8.31 9.879v153.917c0 6.767-1.044 12.49-10.387 13.008l-161.487 9.361c-9.343.517-13.489-2.594-13.489-10.921M212.377 89.53c1.034 4.681 0 9.362-4.681 9.897l-7.783 1.542v114.404c-6.758 3.637-12.981 5.715-18.18 5.715c-8.308 0-10.386-2.604-16.609-10.396l-50.898-80.079v77.476l16.1 3.646s0 9.362-12.989 9.362l-35.814 2.077c-1.043-2.086 0-7.284 3.63-8.318l9.351-2.595V109.823l-12.98-1.052c-1.044-4.68 1.55-11.439 8.826-11.965l38.426-2.585l52.958 81.113v-71.76l-13.498-1.552c-1.043-5.733 3.111-9.896 8.3-10.404z"/></svg>
@@ -0,0 +1,104 @@
1
+ // Top bar, side panel and peek chrome shared by every screen.
2
+ import { icon } from "./icons.js";
3
+ import { objectIcon } from "./rich.js";
4
+ import { ancestors, navigate, routeFor, state } from "./state.js";
5
+ import { $, el, iconButton } from "./ui.js";
6
+
7
+ /** Breadcrumb from the index: ancestors (oldest first) then the current node; collapses the middle like the product. */
8
+ export function setBreadcrumb(current, { extra } = {}) {
9
+ const host = $("#breadcrumb");
10
+ host.replaceChildren();
11
+ if (!current) return;
12
+ const chain = [...ancestors(current).reverse(), current];
13
+ const items = chain.length > 4 ? [chain[0], { ellipsis: true }, chain[chain.length - 2], chain[chain.length - 1]] : chain;
14
+ items.forEach((node, index) => {
15
+ if (index > 0) host.append(el("span", { class: "crumb-sep", text: "/", attrs: { "aria-hidden": "true" } }));
16
+ if (node.ellipsis) {
17
+ host.append(el("span", { class: "crumb ellipsis", text: "…" }));
18
+ return;
19
+ }
20
+ const last = index === items.length - 1;
21
+ const crumb = el(last ? "span" : "a", { class: `crumb ${last ? "current" : ""}`.trim(), attrs: last ? { "aria-current": "page" } : { href: routeHashOf(node) } });
22
+ crumb.append(objectIcon(node.iconObject, node.kind, "crumb-icon"), el("span", { class: "crumb-title", text: node.title }));
23
+ host.append(crumb);
24
+ });
25
+ if (extra) host.append(extra);
26
+ }
27
+
28
+ function routeHashOf(node) {
29
+ const route = routeFor(node);
30
+ return route.name === "database" ? `#/database/${route.id}/table` : `#/page/${route.id}`;
31
+ }
32
+
33
+ export function setPlainBreadcrumb(title, iconName) {
34
+ const host = $("#breadcrumb");
35
+ host.replaceChildren(el("span", { class: "crumb current" }, [iconName ? icon(iconName, "crumb-icon glyph") : null, el("span", { class: "crumb-title", text: title })]));
36
+ }
37
+
38
+ export function setTopbarActions(elements) {
39
+ const host = $("#topbar-actions");
40
+ host.replaceChildren(...elements.filter(Boolean));
41
+ }
42
+
43
+ /** Right-hand panel (comments, markdown, updates). */
44
+ export function openSidePanel(title, content, { onClose } = {}) {
45
+ const panel = $("#side-panel");
46
+ panel.replaceChildren(
47
+ el("div", { class: "panel-head" }, [el("span", { class: "panel-title", text: title }), iconButton("close", "Close panel", { onClick: () => closeSidePanel() })]),
48
+ el("div", { class: "panel-body" }, content),
49
+ );
50
+ panel.hidden = false;
51
+ panel.onclose = onClose;
52
+ document.body.classList.add("has-side-panel");
53
+ }
54
+ export function closeSidePanel() {
55
+ const panel = $("#side-panel");
56
+ if (panel.hidden) return;
57
+ panel.hidden = true;
58
+ document.body.classList.remove("has-side-panel");
59
+ const onClose = panel.onclose;
60
+ panel.onclose = undefined;
61
+ onClose?.();
62
+ }
63
+ export const sidePanelOpen = () => !$("#side-panel").hidden;
64
+
65
+ /** Side peek: a page opened from a database view, on the right half like the product's default "Side peek". */
66
+ export function openPeek(content, { onClose, onExpand } = {}) {
67
+ const peek = $("#peek");
68
+ const head = el("div", { class: "peek-head" }, [
69
+ iconButton("double_chevron_left", "Close peek", { class: "peek-close-btn", onClick: () => closePeek() }),
70
+ onExpand ? iconButton("expand", "Open as page", { onClick: onExpand }) : null,
71
+ ]);
72
+ peek.replaceChildren(head, el("div", { class: "peek-body" }, content));
73
+ peek.hidden = false;
74
+ peek.onclose = onClose;
75
+ document.body.classList.add("has-peek");
76
+ }
77
+ export function closePeek() {
78
+ const peek = $("#peek");
79
+ if (peek.hidden) return;
80
+ peek.hidden = true;
81
+ document.body.classList.remove("has-peek");
82
+ const onClose = peek.onclose;
83
+ peek.onclose = undefined;
84
+ onClose?.();
85
+ }
86
+ export const peekOpen = () => !$("#peek").hidden;
87
+ export const peekBody = () => $("#peek .peek-body");
88
+
89
+ /** Product-style full-screen states used by every screen. */
90
+ export function emptyState(title, text, actions = []) {
91
+ return el("div", { class: "empty-state" }, [el("p", { class: "empty-title", text: title }), text ? el("p", { class: "empty-text", text }) : null, actions.length > 0 ? el("div", { class: "empty-actions" }, actions) : null]);
92
+ }
93
+
94
+ export function skeleton(lines = 6) {
95
+ const host = el("div", { class: "skeleton", attrs: { "aria-hidden": "true" } });
96
+ for (let index = 0; index < lines; index += 1) host.append(el("div", { class: `skeleton-line w${(index % 4) + 1}` }));
97
+ return host;
98
+ }
99
+
100
+ export function goTo(node) {
101
+ navigate(routeFor(node));
102
+ }
103
+
104
+ export { state };
@@ -0,0 +1,83 @@
1
+ // Cover picker: the product's Gallery / Upload / Link / Unsplash popover. Gallery and Link write `cover` through
2
+ // pages.update; Upload and Unsplash need file hosting or a remote service and are visibly disabled.
3
+ import { closePopovers, el, openPopover, textButton } from "./ui.js";
4
+ import { state } from "./state.js";
5
+
6
+ /** Gradient seed used to draw a cover (external images are never fetched inside the test environment). */
7
+ export function coverSeed(text) {
8
+ let hash = 0;
9
+ for (const character of String(text)) hash = (hash * 31 + character.charCodeAt(0)) >>> 0;
10
+ return hash % 6;
11
+ }
12
+
13
+ /** Cover images already used in the workspace (the Tool stores covers as external links; none are invented here). */
14
+ export function workspaceCovers() {
15
+ const urls = new Set();
16
+ for (const node of [...state.pages.values(), ...state.databases.values()]) {
17
+ const url = node.raw?.cover?.external?.url;
18
+ if (typeof url === "string" && url !== "") urls.add(url);
19
+ }
20
+ return [...urls].slice(0, 9).map((url) => ({ seed: coverSeed(url), url }));
21
+ }
22
+
23
+ export function openCoverPicker(anchor, currentUrl, onPick, { startWith = "gallery" } = {}) {
24
+ const GALLERY = workspaceCovers();
25
+ const box = el("div", { class: "cover-picker", attrs: { "aria-label": "Change cover" } });
26
+ const tabs = el("div", { class: "cover-tabs", attrs: { role: "tablist" } });
27
+ const body = el("div", { class: "cover-body" });
28
+ const tab = (label, { disabled = false, selected = false } = {}) => {
29
+ const button = el("button", { class: "cover-tab", title: disabled ? `${label}: not simulated by this Tool` : label, attrs: { type: "button", role: "tab", "aria-selected": String(selected) } });
30
+ button.append(el("span", { text: label }));
31
+ button.disabled = disabled;
32
+ return button;
33
+ };
34
+ const gallery = tab("Gallery", { selected: true });
35
+ const upload = tab("Upload", { disabled: true });
36
+ const link = tab("Link");
37
+ const unsplash = tab("Unsplash", { disabled: true });
38
+ const remove = el("button", { class: "cover-remove", text: "Remove", attrs: { type: "button" } });
39
+ remove.disabled = !currentUrl;
40
+ remove.addEventListener("click", () => { closePopovers(); onPick(null); });
41
+ tabs.append(gallery, upload, link, unsplash, el("span", { class: "cover-tabs-gap" }), remove);
42
+
43
+ const showGallery = () => {
44
+ gallery.setAttribute("aria-selected", "true");
45
+ link.setAttribute("aria-selected", "false");
46
+ const grid = el("div", { class: "cover-grid" });
47
+ GALLERY.forEach(({ seed, url }, index) => {
48
+ const swatch = el("button", { class: `cover-swatch ${url === currentUrl ? "current" : ""}`.trim(), title: url, attrs: { type: "button", "aria-label": `Use cover ${index + 1}: ${url}` } });
49
+ swatch.dataset.seed = String(seed);
50
+ swatch.addEventListener("click", () => { closePopovers(); onPick(url); });
51
+ grid.append(swatch);
52
+ });
53
+ const group = el("div", { class: "cover-group", text: "Used in this workspace" });
54
+ if (GALLERY.length === 0) grid.append(el("p", { class: "cover-note-text", text: "No covers are used in this workspace yet. Add one from a link." }));
55
+ body.replaceChildren(group, grid, el("p", { class: "cover-note-text", text: "Upload and Unsplash are not simulated by this Tool." }));
56
+ };
57
+ const showLink = () => {
58
+ gallery.setAttribute("aria-selected", "false");
59
+ link.setAttribute("aria-selected", "true");
60
+ const id = "cover-link-input";
61
+ const input = el("input", { class: "cover-link", attrs: { id, type: "url", placeholder: "Paste an image link…", "aria-label": "Image link", value: currentUrl ?? "" } });
62
+ const submit = textButton("Submit", { class: "primary" });
63
+ const run = () => {
64
+ const value = input.value.trim();
65
+ if (!/^https?:\/\/\S+$/.test(value)) {
66
+ input.setAttribute("aria-invalid", "true");
67
+ return;
68
+ }
69
+ closePopovers();
70
+ onPick(value);
71
+ };
72
+ submit.addEventListener("click", run);
73
+ input.addEventListener("keydown", (event) => { if (event.key === "Enter") { event.preventDefault(); run(); } });
74
+ body.replaceChildren(input, submit, el("p", { class: "cover-note-text", text: "Works with any image from the web. Images are stored as links and drawn as a gradient here." }));
75
+ input.focus();
76
+ };
77
+ gallery.addEventListener("click", showGallery);
78
+ link.addEventListener("click", showLink);
79
+ if (startWith === "link") showLink();
80
+ else showGallery();
81
+ box.append(tabs, body);
82
+ return openPopover(anchor, box, { width: 440, align: "end", focusFirst: false });
83
+ }