From 9a70a04237fb85643735db0049f3e2b4bc78c0f6 Mon Sep 17 00:00:00 2001 From: SirEdvin Date: Thu, 16 Jul 2026 08:30:21 +0000 Subject: [PATCH 01/10] style: redesign typed peripheral docs with modern indigo theme --- .../documentation/theme/digitalitems.css | 363 +++++++++++++----- .../documentation/theme/plugin.mjs | 9 +- 2 files changed, 266 insertions(+), 106 deletions(-) diff --git a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css index 604ba7f..68bb81d 100644 --- a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css +++ b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css @@ -1,58 +1,63 @@ +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap"); + :root { - --di-ink: #071116; - --di-panel: #0c1c23; - --di-panel-raised: #102832; - --di-grid: rgba(75, 225, 210, 0.08); - --di-cyan: #4be1d2; - --di-cyan-soft: #a0fff4; - --di-amber: #ffbd59; - --di-muted: #9fb4ba; - --color-background: var(--di-ink); - --color-background-secondary: var(--di-panel); - --color-background-active: var(--di-panel-raised); - --color-accent: var(--di-cyan); - --color-text: #e4f2f2; - --color-text-aside: var(--di-muted); - --color-link: var(--di-cyan-soft); - --color-ts-keyword: #ff8d72; - --color-ts-string: #b7e67b; + --di-accent: #6366f1; + --di-accent-hover: #818cf8; + --di-accent-dark: #4f46e5; + --di-accent-subtle: rgba(99, 102, 241, 0.08); + --di-bg: #0c0e16; + --di-bg-secondary: #141622; + --di-bg-elevated: #1a1d2e; + --di-bg-code: #111322; + --di-border: rgba(99, 102, 241, 0.12); + --di-border-subtle: rgba(148, 163, 184, 0.08); + --di-text-primary: #e2e8f0; + --di-text-secondary: #8892b0; + --di-text-muted: #5a6380; + --di-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.12); + --di-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.18); + --color-background: var(--di-bg); + --color-background-secondary: var(--di-bg-secondary); + --color-background-active: var(--di-bg-elevated); + --color-accent: var(--di-accent); + --color-text: var(--di-text-primary); + --color-text-aside: var(--di-text-secondary); + --color-link: #818cf8; + --color-ts-keyword: #c084fc; + --color-ts-string: #34d399; + --color-ts-number: #fb923c; } html[data-theme="light"] { - --di-ink: #eef6f4; - --di-panel: #deebe8; - --di-panel-raised: #cce1dc; - --di-grid: rgba(0, 104, 104, 0.09); - --di-cyan: #007f7c; - --di-cyan-soft: #006a69; - --di-amber: #a55a00; - --di-muted: #496669; - --color-text: #102b2e; + --di-accent: #4f46e5; + --di-accent-hover: #4338ca; + --di-accent-dark: #3730a3; + --di-accent-subtle: rgba(79, 70, 229, 0.06); + --di-bg: #ffffff; + --di-bg-secondary: #f8fafc; + --di-bg-elevated: #eef2ff; + --di-bg-code: #f1f5f9; + --di-border: rgba(79, 70, 229, 0.1); + --di-border-subtle: rgba(148, 163, 184, 0.12); + --di-text-primary: #0f172a; + --di-text-secondary: #475569; + --di-text-muted: #94a3b8; + --di-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04); + --di-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.06); + --color-text: var(--di-text-primary); + --color-link: #4f46e5; } body { - background-color: var(--di-ink); - background-image: - linear-gradient(var(--di-grid) 1px, transparent 1px), - linear-gradient(90deg, var(--di-grid) 1px, transparent 1px), - radial-gradient(circle at 83% 9%, rgba(75, 225, 210, 0.11), transparent 28rem); - background-size: 48px 48px, 48px 48px, auto; - font-family: "Trebuchet MS", "Gill Sans", sans-serif; -} - -.di-signal-line { - position: fixed; - z-index: 100; - top: 0; - right: 0; - left: 0; - height: 3px; - background: linear-gradient(90deg, var(--di-cyan) 0 12%, transparent 12% 13%, var(--di-amber) 13% 18%, transparent 18% 100%); - box-shadow: 0 0 18px color-mix(in srgb, var(--di-cyan) 65%, transparent); + font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + line-height: 1.6; + background-color: var(--di-bg); } .container-main { - backdrop-filter: blur(2px); + backdrop-filter: none; } .col-content { @@ -60,109 +65,271 @@ body { } .col-content::before { - content: "DIGITAL ITEMS // PERIPHERAL ARCHIVE"; - display: block; - margin: 1.5rem 0 2rem; - color: var(--di-cyan); - font: 700 0.72rem/1.2 "Lucida Console", Monaco, monospace; - letter-spacing: 0.16em; + content: none; + display: none; } -h1, -h2, -h3, -h4, +h1, h2, h3, h4, h5, h6, .site-menu, .tsd-page-title { - font-family: "Lucida Console", Monaco, monospace; - letter-spacing: -0.035em; + font-family: "Inter", system-ui, -apple-system, sans-serif; + letter-spacing: -0.025em; } h1 { + font-weight: 800; + font-size: 2rem; + line-height: 1.25; text-wrap: balance; + margin-bottom: 1rem; } h2 { - border-bottom: 1px solid color-mix(in srgb, var(--di-cyan) 28%, transparent); - padding-bottom: 0.55rem; + font-weight: 700; + font-size: 1.5rem; + line-height: 1.3; + border-bottom: 1px solid var(--di-border); + padding-bottom: 0.5rem; + margin-top: 2.5rem; + margin-bottom: 1rem; } -.tsd-panel, -.tsd-member, -pre, -table { - border: 1px solid color-mix(in srgb, var(--di-cyan) 18%, transparent); - border-radius: 4px; - box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.14); +h3 { + font-weight: 600; + font-size: 1.25rem; + line-height: 1.4; + margin-top: 1.75rem; + margin-bottom: 0.75rem; +} + +h4 { + font-weight: 600; + font-size: 1.1rem; + margin-top: 1.5rem; + margin-bottom: 0.5rem; +} + +p { + margin-bottom: 1rem; +} + +ul, ol { + margin-bottom: 1rem; + padding-left: 1.5rem; +} + +li { + margin-bottom: 0.35rem; +} + +.tsd-panel { + border: 1px solid var(--di-border); + border-radius: 10px; + box-shadow: var(--di-shadow-sm); + background: var(--di-bg-secondary); +} + +.tsd-member { + border: 1px solid var(--di-border); + border-radius: 10px; + box-shadow: var(--di-shadow-sm); } .tsd-signature, code, pre { - font-family: "Lucida Console", Monaco, monospace; + font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", Consolas, monospace; +} + +code { + background: var(--di-bg-code); + border: 1px solid var(--di-border-subtle); + border-radius: 5px; + padding: 0.1em 0.4em; + font-size: 0.875em; + color: var(--di-accent-hover); +} + +html[data-theme="light"] code { + color: var(--di-accent); +} + +pre { + background: var(--di-bg-code); + border: 1px solid var(--di-border); + border-radius: 10px; + padding: 1rem 1.25rem; + overflow-x: auto; + box-shadow: var(--di-shadow-sm); +} + +pre code { + background: none; + border: none; + padding: 0; + font-size: 0.85rem; + line-height: 1.65; + color: var(--di-text-primary); } a { + color: var(--color-link); + text-decoration: none; text-underline-offset: 0.2em; + transition: color 0.15s ease; } a:hover { - color: var(--di-amber); + color: var(--di-accent-hover); +} + +table { + border: 1px solid var(--di-border); + border-radius: 10px; + overflow: hidden; + box-shadow: var(--di-shadow-sm); +} + +th, td { + border-color: var(--di-border-subtle); +} + +.tsd-page-toolbar { + background: var(--di-bg); + border-bottom: 1px solid var(--di-border); +} + +.tsd-page-toolbar .title { + font-family: "Inter", system-ui, sans-serif; + font-weight: 700; + color: var(--di-text-primary); +} + +.di-signal-line { + display: none !important; } .di-version-panel { - margin: 0.75rem 0 1.35rem; - padding: 0.8rem; - border: 1px solid color-mix(in srgb, var(--di-cyan) 42%, transparent); - background: linear-gradient(135deg, var(--di-panel-raised), var(--di-panel)); - clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%); + margin: 0.5rem 0 1rem; + padding: 0.6rem 0.75rem; + border: 1px solid var(--di-border); + border-radius: 8px; + background: var(--di-bg-secondary); } .di-version-label { display: block; - margin-bottom: 0.45rem; - color: var(--di-amber); - font: 700 0.64rem/1 "Lucida Console", Monaco, monospace; - letter-spacing: 0.12em; + margin-bottom: 0.3rem; + color: var(--di-accent); + font: 600 0.68rem/1.2 "Inter", system-ui, sans-serif; + letter-spacing: 0.05em; + text-transform: uppercase; } #di-version-select { width: 100%; - padding: 0.5rem 1.8rem 0.5rem 0.55rem; - border: 1px solid color-mix(in srgb, var(--di-cyan) 55%, transparent); - border-radius: 2px; - color: var(--color-text); - background: var(--di-ink); + padding: 0.35rem 0.5rem; + border: 1px solid var(--di-border); + border-radius: 6px; + color: var(--di-text-primary); + background: var(--di-bg); + font-family: "Inter", system-ui, sans-serif; + font-size: 0.82rem; } #di-version-select:focus-visible { - outline: 2px solid var(--di-amber); - outline-offset: 2px; + outline: 2px solid var(--di-accent); + outline-offset: 1px; } -@media (max-width: 769px) { - .col-content::before { - margin-top: 0.75rem; - font-size: 0.64rem; - } +.tsd-typography { + line-height: 1.7; +} - .tsd-signature, - pre, - table { - max-width: 100%; - overflow-x: auto; - } +.tsd-anchor-link { + color: var(--di-accent); +} + +.tsd-anchor-link:hover { + color: var(--di-accent-hover); +} + +.tsd-kind-icon { + color: var(--di-accent); +} + +.tsd-navigation a, +.tsd-nav-link { + font-family: "Inter", system-ui, sans-serif; +} + +.tsd-accordion-summary { + font-family: "Inter", system-ui, sans-serif; +} + +.tsd-filter-visibility .settings-label { + font-family: "Inter", system-ui, sans-serif; + font-weight: 600; + font-size: 0.8rem; + letter-spacing: 0.03em; + text-transform: uppercase; + color: var(--di-text-secondary); +} + +#tsd-theme { + font-family: "Inter", system-ui, sans-serif; + border: 1px solid var(--di-border); + border-radius: 6px; + padding: 0.3rem 0.5rem; + background: var(--di-bg); + color: var(--di-text-primary); + font-size: 0.82rem; +} + +.tsd-search-input { + font-family: "Inter", system-ui, sans-serif; + border: 1px solid var(--di-border); + border-radius: 8px; +} + +.tsd-generator { + border-top: 1px solid var(--di-border); + padding-top: 1rem; + margin-top: 2rem; + color: var(--di-text-muted); + font-size: 0.82rem; +} + +footer { + border-top: 1px solid var(--di-border); } @media (prefers-reduced-motion: no-preference) { .col-content > * { - animation: di-reveal 360ms ease-out both; + animation: di-fade-in 180ms ease-out both; } - @keyframes di-reveal { + @keyframes di-fade-in { from { opacity: 0; - transform: translateY(8px); + transform: translateY(3px); } } + + a, .tsd-panel, .tsd-member { + transition: all 0.15s ease; + } +} + +@media (max-width: 769px) { + h1 { font-size: 1.5rem; } + h2 { font-size: 1.25rem; } + h3 { font-size: 1.1rem; } + + .tsd-signature, + pre, + table { + max-width: 100%; + overflow-x: auto; + } } diff --git a/projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs b/projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs index 1c7e274..581cc9c 100644 --- a/projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs +++ b/projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs @@ -1,18 +1,11 @@ import { JSX } from "typedoc"; export function load(app) { - app.renderer.hooks.on("body.begin", () => - JSX.createElement("div", { - class: "di-signal-line", - "aria-hidden": "true", - }) - ); - app.renderer.hooks.on("sidebar.begin", () => JSX.createElement( "section", { class: "di-version-panel", "aria-label": "Documentation version" }, - JSX.createElement("span", { class: "di-version-label" }, "DOCUMENTATION VERSION"), + JSX.createElement("span", { class: "di-version-label" }, "Version"), JSX.createElement( "select", { id: "di-version-select", disabled: true }, From ee39ad4f477c0f4bf3276023101885d383aa467f Mon Sep 17 00:00:00 2001 From: SirEdvin Date: Thu, 16 Jul 2026 09:03:08 +0000 Subject: [PATCH 02/10] docs: add player-focused peripheral pages --- .../documentation/guides/advanced-items.md | 20 +- .../documentation/guides/basic-digitizer.md | 27 ++- .../documentation/index.md | 35 ++- .../documentation/theme/digitalitems.css | 215 +++++++++++++++++- 4 files changed, 272 insertions(+), 25 deletions(-) diff --git a/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md b/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md index f245942..ab3d3d3 100644 --- a/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md +++ b/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md @@ -1,9 +1,27 @@ -# Advanced Items +# Advanced Digitizer + +
+
Advanced digitizer block
+
MULTI-STORAGE PERIPHERAL

The advanced digitizer moves items, fluids, and energy between attached storage and durable digital identifiers.

Peripheral type
advanced_digitizer
Storage
Items, fluids, energy
Failure style
Recoverable result pairs
+
+ +## At a glance + +| Method | Returns | What it does | +| --- | --- | --- | +| `digitize(mode, source?, filter?, limit?, destination?)` | `id, error` | Extracts content and creates or extends an ID. | +| `rematerialize(mode, id, limit?, destination?)` | `amount, error` | Inserts stored content into a destination. | +| `get(mode, id)` | ID information or `nil` | Inspects current contents and decay metadata. | +| `refresh(mode, id)` | Boolean | Refreshes an ID if it still exists. | +| `getConfiguration()` | Configuration table | Reports decay and per-mode stack limits. | The advanced digitizer can extract from its internal inventory with source `self`, or from an attached inventory/item storage by peripheral name. The source defaults to `self`. +> **Mode matters:** an identifier belongs to exactly one of `item`, `fluid`, or +> `energy`. Always use the same mode when inspecting, refreshing, or restoring it. + Item filters may be: - A one-based slot number. This requires slotted storage. diff --git a/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md b/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md index 8ce2709..98b6786 100644 --- a/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md +++ b/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md @@ -1,8 +1,27 @@ # Basic Digitizer -The basic digitizer handles items in its own inventory. Slots are one-based and -default to slot 1. `digitize()` removes the complete stack; `digitizeAmount()` -requires an exact positive amount no greater than the physical stack. +
+
Digitizer block
+
ITEM PERIPHERAL

The basic digitizer turns physical item stacks into portable digital identifiers and restores them through its internal inventory.

Peripheral type
digitizer
Storage
Items
Failure style
Lua errors
+
+ +## At a glance + +| Method | Returns | What it does | +| --- | --- | --- | +| `digitize(slot?)` | Digital ID | Digitizes the complete stack in a slot. | +| `digitizeAmount(amount, slot?)` | Digital ID | Digitizes an exact number of items. | +| `rematerialize(id)` | Item count | Restores as much of an ID as the inventory accepts. | +| `rematerializeAmount(id, amount)` | Item count | Requests an exact portion of an ID. | +| `getIDInfo(id)` | Item and timing data | Inspects the contents and decay deadline. | +| `refresh(id)` | Nothing | Resets the identifier's decay deadline. | + +Slots are one-based and default to slot 1. `digitize()` removes the complete +stack; `digitizeAmount()` requires an exact positive amount no greater than the +physical stack. + +> **Inherited inventory API:** `size`, `list`, `getItemDetail`, `getItemLimit`, +> `pushItems`, and `pullItems` are also available on this peripheral. ## Round trip a partial stack @@ -29,6 +48,8 @@ local inserted = digitizer.rematerializeAmount(id, 8) print("Inserted " .. inserted) ``` +## Errors and remaining items + All failures throw. Empty slots, non-positive amounts, requesting more than the physical or digital count, and unknown or decayed identifiers are errors. diff --git a/projects/typed-peripheral-digitalitems/documentation/index.md b/projects/typed-peripheral-digitalitems/documentation/index.md index e992658..c3dd707 100644 --- a/projects/typed-peripheral-digitalitems/documentation/index.md +++ b/projects/typed-peripheral-digitalitems/documentation/index.md @@ -1,16 +1,27 @@ -# Digital Items Typed Peripheral API +# Digital Items Peripheral Manual -This package supplies TypeScriptToLua declarations and peripheral providers for -Digital Items 3. It covers the item-only `digitizer` and the multi-storage -`advanced_digitizer` peripherals. +Player-focused documentation and typed APIs for the Digital Items 3 peripherals. +Use these pages whether you write programs directly in CraftOS Lua or compile +TypeScript with TypeScriptToLua. -## Choose a peripheral +## Choose your digitizer -- Use the basic digitizer for simple item workflows through its internal inventory. -- Use the advanced digitizer to address remote item, fluid, or energy storage, merge content into existing identifiers, and handle recoverable failures. -- Both peripherals inherit inventory methods. The advanced digitizer additionally reports decay and stack limits through `getConfiguration()`. + -## Documentation +Both peripherals include the standard CC:Tweaked inventory methods. The advanced +digitizer also exposes configured decay and storage limits through +`getConfiguration()`. + +## Start here - [Getting started](getting-started.md) - [Digital identifiers](digital-identifiers.md) @@ -18,6 +29,6 @@ Digital Items 3. It covers the item-only `digitizer` and the multi-storage - [Advanced item guide](guides/advanced-items.md) - [Fluid and energy guide](guides/fluids-and-energy.md) -The generated API reference documents every exported type, method overload, and -provider. Guide examples are shown first in TypeScriptToLua and then in direct -CraftOS Lua. +The **Guides** explain behavior in player terms and include both Lua and +TypeScript examples. The **API reference** is the precise source for signatures, +overloads, return types, and provider declarations. diff --git a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css index 604ba7f..66d552d 100644 --- a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css +++ b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css @@ -1,7 +1,7 @@ :root { - --di-ink: #071116; - --di-panel: #0c1c23; - --di-panel-raised: #102832; + --di-ink: #10191d; + --di-panel: #16252b; + --di-panel-raised: #1c3038; --di-grid: rgba(75, 225, 210, 0.08); --di-cyan: #4be1d2; --di-cyan-soft: #a0fff4; @@ -37,7 +37,9 @@ body { linear-gradient(90deg, var(--di-grid) 1px, transparent 1px), radial-gradient(circle at 83% 9%, rgba(75, 225, 210, 0.11), transparent 28rem); background-size: 48px 48px, 48px 48px, auto; - font-family: "Trebuchet MS", "Gill Sans", sans-serif; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 16px; + line-height: 1.7; } .di-signal-line { @@ -57,12 +59,13 @@ body { .col-content { position: relative; + max-width: 920px; } .col-content::before { content: "DIGITAL ITEMS // PERIPHERAL ARCHIVE"; display: block; - margin: 1.5rem 0 2rem; + margin: 1.25rem 0 1.5rem; color: var(--di-cyan); font: 700 0.72rem/1.2 "Lucida Console", Monaco, monospace; letter-spacing: 0.16em; @@ -74,17 +77,20 @@ h3, h4, .site-menu, .tsd-page-title { - font-family: "Lucida Console", Monaco, monospace; - letter-spacing: -0.035em; + font-family: Inter, ui-sans-serif, system-ui, sans-serif; + letter-spacing: -0.025em; } h1 { text-wrap: balance; + font-size: clamp(2rem, 5vw, 3rem); + line-height: 1.12; } h2 { border-bottom: 1px solid color-mix(in srgb, var(--di-cyan) 28%, transparent); padding-bottom: 0.55rem; + margin-top: 2.4rem; } .tsd-panel, @@ -92,8 +98,8 @@ h2 { pre, table { border: 1px solid color-mix(in srgb, var(--di-cyan) 18%, transparent); - border-radius: 4px; - box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.14); + border-radius: 8px; + box-shadow: 0 10px 28px rgba(0, 0, 0, 0.12); } .tsd-signature, @@ -110,6 +116,167 @@ a:hover { color: var(--di-amber); } +.col-content p, +.col-content li { + max-width: 78ch; +} + +.col-content table { + width: 100%; + border-collapse: collapse; + overflow: hidden; +} + +.col-content th { + color: var(--di-cyan-soft); + background: var(--di-panel-raised); + text-align: left; +} + +.col-content th, +.col-content td { + padding: 0.8rem 1rem; + border-bottom: 1px solid color-mix(in srgb, var(--di-cyan) 14%, transparent); + vertical-align: top; +} + +.col-content blockquote { + margin: 1.5rem 0; + padding: 0.8rem 1.1rem; + border-left: 4px solid var(--di-amber); + background: color-mix(in srgb, var(--di-amber) 7%, var(--di-panel)); +} + +.col-content blockquote p { + margin: 0; +} + +.di-peripheral-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; + margin: 1.5rem 0 2rem; +} + +.di-peripheral-card, +.di-peripheral-hero { + border: 1px solid color-mix(in srgb, var(--di-cyan) 32%, transparent); + background: linear-gradient(145deg, var(--di-panel-raised), var(--di-panel)); +} + +.di-peripheral-card { + display: flex; + gap: 1.2rem; + min-height: 190px; + padding: 1.25rem; + border-radius: 10px; + color: var(--color-text); + text-decoration: none; + transition: border-color 160ms ease, transform 160ms ease; +} + +.di-peripheral-card:hover { + border-color: var(--di-cyan); + color: var(--color-text); + transform: translateY(-3px); +} + +.di-peripheral-card--advanced, +.di-peripheral-hero--advanced { + border-color: color-mix(in srgb, var(--di-amber) 35%, transparent); +} + +.di-peripheral-image { + display: grid; + flex: 0 0 128px; + min-height: 128px; + place-items: center; + border-radius: 6px; + background-color: #0a1114; + background-image: linear-gradient(45deg, rgba(255,255,255,.035) 25%, transparent 25%, transparent 75%, rgba(255,255,255,.035) 75%), linear-gradient(45deg, rgba(255,255,255,.035) 25%, transparent 25%, transparent 75%, rgba(255,255,255,.035) 75%); + background-position: 0 0, 8px 8px; + background-size: 16px 16px; +} + +.di-peripheral-image img { + width: 112px; + height: 112px; + image-rendering: pixelated; +} + +.di-peripheral-copy { + display: flex; + flex-direction: column; +} + +.di-peripheral-copy strong { + font-size: 1.25rem; +} + +.di-peripheral-copy small { + margin: 0.25rem 0 0.75rem; + color: var(--di-muted); +} + +.di-peripheral-copy b { + margin-top: auto; + color: var(--di-cyan); + font-size: 0.82rem; +} + +.di-peripheral-hero { + display: grid; + grid-template-columns: 176px minmax(0, 1fr); + gap: 1.75rem; + margin: 1rem 0 2rem; + padding: 1.5rem; + border-radius: 10px; +} + +.di-peripheral-hero .di-peripheral-image { + min-height: 176px; +} + +.di-peripheral-hero .di-peripheral-image img { + width: 144px; + height: 144px; +} + +.di-eyebrow { + color: var(--di-amber); + font: 700 0.7rem/1 "Lucida Console", Monaco, monospace; + letter-spacing: 0.12em; +} + +.di-peripheral-hero p { + margin: 0.55rem 0 1rem; + font-size: 1.05rem; +} + +.di-peripheral-hero dl { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.7rem; + margin: 0; +} + +.di-peripheral-hero dl div { + min-width: 0; +} + +.di-peripheral-hero dt { + color: var(--di-muted); + font-size: 0.68rem; + text-transform: uppercase; + letter-spacing: 0.06em; +} + +.di-peripheral-hero dd { + margin: 0.2rem 0 0; + overflow-wrap: anywhere; + font-weight: 650; +} + .di-version-panel { margin: 0.75rem 0 1.35rem; padding: 0.8rem; @@ -152,6 +319,36 @@ a:hover { max-width: 100%; overflow-x: auto; } + + .di-peripheral-grid { + grid-template-columns: 1fr; + } + + .di-peripheral-hero { + grid-template-columns: 1fr; + } + + .di-peripheral-hero .di-peripheral-image { + min-height: 144px; + } + + .di-peripheral-hero dl { + grid-template-columns: 1fr 1fr; + } +} + +@media (max-width: 480px) { + .di-peripheral-card { + flex-direction: column; + } + + .di-peripheral-image { + flex-basis: auto; + } + + .di-peripheral-hero dl { + grid-template-columns: 1fr; + } } @media (prefers-reduced-motion: no-preference) { From 7300a06886017ee6b029cfeda14681515c982937 Mon Sep 17 00:00:00 2001 From: SirEdvin Date: Thu, 16 Jul 2026 09:08:10 +0000 Subject: [PATCH 03/10] docs: generate peripheral method reference --- .../documentation/guides/advanced-items.md | 14 ++--- .../documentation/guides/basic-digitizer.md | 15 ++--- .../documentation/theme/digitalitems.css | 55 +++++++++++++++++++ .../documentation/theme/plugin.mjs | 53 +++++++++++++++++- 4 files changed, 115 insertions(+), 22 deletions(-) diff --git a/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md b/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md index ab3d3d3..42faf03 100644 --- a/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md +++ b/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md @@ -1,3 +1,7 @@ +--- +peripheralInterface: AdvancedDigitizer +--- + # Advanced Digitizer
@@ -5,16 +9,6 @@
MULTI-STORAGE PERIPHERAL

The advanced digitizer moves items, fluids, and energy between attached storage and durable digital identifiers.

Peripheral type
advanced_digitizer
Storage
Items, fluids, energy
Failure style
Recoverable result pairs
-## At a glance - -| Method | Returns | What it does | -| --- | --- | --- | -| `digitize(mode, source?, filter?, limit?, destination?)` | `id, error` | Extracts content and creates or extends an ID. | -| `rematerialize(mode, id, limit?, destination?)` | `amount, error` | Inserts stored content into a destination. | -| `get(mode, id)` | ID information or `nil` | Inspects current contents and decay metadata. | -| `refresh(mode, id)` | Boolean | Refreshes an ID if it still exists. | -| `getConfiguration()` | Configuration table | Reports decay and per-mode stack limits. | - The advanced digitizer can extract from its internal inventory with source `self`, or from an attached inventory/item storage by peripheral name. The source defaults to `self`. diff --git a/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md b/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md index 98b6786..b3eb5c6 100644 --- a/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md +++ b/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md @@ -1,3 +1,7 @@ +--- +peripheralInterface: Digitizer +--- + # Basic Digitizer
@@ -5,17 +9,6 @@
ITEM PERIPHERAL

The basic digitizer turns physical item stacks into portable digital identifiers and restores them through its internal inventory.

Peripheral type
digitizer
Storage
Items
Failure style
Lua errors
-## At a glance - -| Method | Returns | What it does | -| --- | --- | --- | -| `digitize(slot?)` | Digital ID | Digitizes the complete stack in a slot. | -| `digitizeAmount(amount, slot?)` | Digital ID | Digitizes an exact number of items. | -| `rematerialize(id)` | Item count | Restores as much of an ID as the inventory accepts. | -| `rematerializeAmount(id, amount)` | Item count | Requests an exact portion of an ID. | -| `getIDInfo(id)` | Item and timing data | Inspects the contents and decay deadline. | -| `refresh(id)` | Nothing | Resets the identifier's decay deadline. | - Slots are one-based and default to slot 1. `digitize()` removes the complete stack; `digitizeAmount()` requires an exact positive amount no greater than the physical stack. diff --git a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css index 66d552d..8b44687 100644 --- a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css +++ b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css @@ -277,6 +277,61 @@ a:hover { font-weight: 650; } +.di-method-reference { + margin-top: 3rem; +} + +.di-generated-note { + margin-bottom: 1.25rem; + color: var(--di-muted); + font-size: 0.9rem; +} + +.di-method-card { + margin: 1rem 0; + border: 1px solid color-mix(in srgb, var(--di-cyan) 24%, transparent); + border-radius: 8px; + background: var(--di-panel); + box-shadow: 0 8px 22px rgba(0, 0, 0, 0.1); + overflow: hidden; +} + +.di-method-card > h3 { + margin: 0; + padding: 0.75rem 1rem; + border-bottom: 1px solid color-mix(in srgb, var(--di-cyan) 18%, transparent); + background: var(--di-panel-raised); + color: var(--di-cyan-soft); + font-size: 1rem; +} + +.di-method-overload { + padding: 1rem; +} + +.di-method-overload + .di-method-overload { + border-top: 1px dashed color-mix(in srgb, var(--di-cyan) 28%, transparent); +} + +.di-method-overload .tsd-signature { + margin: 0 0 1rem; + padding: 0.8rem; + box-shadow: none; + overflow-x: auto; +} + +.di-method-overload .tsd-comment { + margin: 0.75rem 0; +} + +.di-method-overload h4 { + margin: 1rem 0 0.35rem; + color: var(--di-muted); + font-size: 0.78rem; + letter-spacing: 0.05em; + text-transform: uppercase; +} + .di-version-panel { margin: 0.75rem 0 1.35rem; padding: 0.8rem; diff --git a/projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs b/projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs index 1c7e274..d4d7dd3 100644 --- a/projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs +++ b/projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs @@ -1,4 +1,53 @@ -import { JSX } from "typedoc"; +import { JSX, ReflectionKind } from "typedoc"; + +function renderPeripheralMethods(context) { + if (!context.model.isDocument()) return; + + const interfaceName = context.model.frontmatter.peripheralInterface; + if (typeof interfaceName !== "string") return; + + const peripheral = context.page.project + .getReflectionsByKind(ReflectionKind.Interface) + .find((reflection) => reflection.name === interfaceName); + if (!peripheral?.children) return; + + const methods = peripheral.children.filter( + (reflection) => reflection.kindOf(ReflectionKind.Method) && reflection.signatures?.length + ); + if (methods.length === 0) return; + + context.page.pageHeadings.push({ + link: "#peripheral-methods", + text: "Peripheral methods", + level: 2, + }); + + return JSX.createElement( + "section", + { class: "di-method-reference", "aria-labelledby": "peripheral-methods" }, + JSX.createElement("h2", { id: "peripheral-methods" }, "Peripheral methods"), + JSX.createElement( + "p", + { class: "di-generated-note" }, + "Generated from the TypeScript interface. Signatures, parameters, return values, and errors stay synchronized with the published API." + ), + ...methods.map((method) => + JSX.createElement( + "article", + { class: "di-method-card" }, + JSX.createElement("h3", null, method.name), + ...method.signatures.map((signature) => + JSX.createElement( + "div", + { class: "di-method-overload" }, + context.memberSignatureTitle(signature), + context.memberSignatureBody(signature, { hideSources: true }) + ) + ) + ) + ) + ); +} export function load(app) { app.renderer.hooks.on("body.begin", () => @@ -20,4 +69,6 @@ export function load(app) { ) ) ); + + app.renderer.hooks.on("content.end", renderPeripheralMethods); } From 31af87b0dddb6009bc87058c5c9bcdbe9d18db90 Mon Sep 17 00:00:00 2001 From: SirEdvin Date: Thu, 16 Jul 2026 09:28:51 +0000 Subject: [PATCH 04/10] docs: add material theme selection --- .../documentation/theme/digitalitems.css | 74 ++++++++++++------- .../documentation/theme/digitalitems.js | 6 ++ .../typedoc.json | 2 + 3 files changed, 56 insertions(+), 26 deletions(-) diff --git a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css index 98ecce8..bba38b1 100644 --- a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css +++ b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css @@ -1,6 +1,34 @@ @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap"); :root { + --di-accent: #4f46e5; + --di-accent-hover: #4338ca; + --di-accent-dark: #3730a3; + --di-accent-subtle: rgba(79, 70, 229, 0.06); + --di-bg: #ffffff; + --di-bg-secondary: #f8fafc; + --di-bg-elevated: #eef2ff; + --di-bg-code: #f1f5f9; + --di-border: rgba(79, 70, 229, 0.1); + --di-border-subtle: rgba(148, 163, 184, 0.12); + --di-text-primary: #0f172a; + --di-text-secondary: #475569; + --di-text-muted: #94a3b8; + --di-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04); + --di-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.06); + --color-background: var(--di-bg); + --color-background-secondary: var(--di-bg-secondary); + --color-background-active: var(--di-bg-elevated); + --color-accent: var(--di-accent); + --color-text: var(--di-text-primary); + --color-text-aside: var(--di-text-secondary); + --color-link: #4f46e5; + --color-ts-keyword: #c084fc; + --color-ts-string: #34d399; + --color-ts-number: #fb923c; +} + +html[data-theme="dark"] { --di-accent: #6366f1; --di-accent-hover: #818cf8; --di-accent-dark: #4f46e5; @@ -16,36 +44,30 @@ --di-text-muted: #5a6380; --di-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.12); --di-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.18); - --color-background: var(--di-bg); - --color-background-secondary: var(--di-bg-secondary); - --color-background-active: var(--di-bg-elevated); - --color-accent: var(--di-accent); --color-text: var(--di-text-primary); - --color-text-aside: var(--di-text-secondary); --color-link: #818cf8; - --color-ts-keyword: #c084fc; - --color-ts-string: #34d399; - --color-ts-number: #fb923c; } -html[data-theme="light"] { - --di-accent: #4f46e5; - --di-accent-hover: #4338ca; - --di-accent-dark: #3730a3; - --di-accent-subtle: rgba(79, 70, 229, 0.06); - --di-bg: #ffffff; - --di-bg-secondary: #f8fafc; - --di-bg-elevated: #eef2ff; - --di-bg-code: #f1f5f9; - --di-border: rgba(79, 70, 229, 0.1); - --di-border-subtle: rgba(148, 163, 184, 0.12); - --di-text-primary: #0f172a; - --di-text-secondary: #475569; - --di-text-muted: #94a3b8; - --di-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04); - --di-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.06); - --color-text: var(--di-text-primary); - --color-link: #4f46e5; +@media (prefers-color-scheme: dark) { + html[data-theme="os"] { + --di-accent: #6366f1; + --di-accent-hover: #818cf8; + --di-accent-dark: #4f46e5; + --di-accent-subtle: rgba(99, 102, 241, 0.08); + --di-bg: #0c0e16; + --di-bg-secondary: #141622; + --di-bg-elevated: #1a1d2e; + --di-bg-code: #111322; + --di-border: rgba(99, 102, 241, 0.12); + --di-border-subtle: rgba(148, 163, 184, 0.08); + --di-text-primary: #e2e8f0; + --di-text-secondary: #8892b0; + --di-text-muted: #5a6380; + --di-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.12); + --di-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.18); + --color-text: var(--di-text-primary); + --color-link: #818cf8; + } } body { diff --git a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.js b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.js index 3f07312..5e11b2e 100644 --- a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.js +++ b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.js @@ -1,4 +1,10 @@ (() => { + const themeSelector = document.querySelector("#tsd-theme"); + if (themeSelector instanceof HTMLSelectElement) { + const systemOption = themeSelector.querySelector('option[value="os"]'); + if (systemOption) systemOption.textContent = "System"; + } + const selector = document.querySelector("#di-version-select"); if (!(selector instanceof HTMLSelectElement)) return; diff --git a/projects/typed-peripheral-digitalitems/typedoc.json b/projects/typed-peripheral-digitalitems/typedoc.json index afc0d6a..9a3b9c5 100644 --- a/projects/typed-peripheral-digitalitems/typedoc.json +++ b/projects/typed-peripheral-digitalitems/typedoc.json @@ -14,6 +14,8 @@ "customCss": "./documentation/theme/digitalitems.css", "customJs": "./documentation/theme/digitalitems.js", "favicon": "./documentation/theme/favicon.svg", + "lightHighlightTheme": "material-theme-lighter", + "darkHighlightTheme": "material-theme-darker", "highlightLanguages": ["typescript", "lua", "json", "bash"], "excludeExternals": true, "basePath": "../..", From c3e31eb64383e552749bc352a29fbdebe816dfd7 Mon Sep 17 00:00:00 2001 From: SirEdvin Date: Thu, 16 Jul 2026 10:09:45 +0000 Subject: [PATCH 05/10] docs: replace TypeDoc with MkDocs --- .github/scripts/docs-site.mjs | 24 +- .github/scripts/docs-site.test.mjs | 19 +- .github/workflows/docs-check.yml | 12 +- .github/workflows/docs-pages.yml | 11 + .../typed-peripheral-digitalitems/.gitignore | 1 + .../build.gradle.kts | 6 +- .../documentation/generate-docs.mjs | 196 ++++++ .../documentation/generate-docs.test.mjs | 34 + .../documentation/guides/advanced-items.md | 4 +- .../documentation/guides/basic-digitizer.md | 4 +- .../documentation/index.md | 8 +- .../documentation/theme/digitalitems.css | 633 ++---------------- .../documentation/theme/digitalitems.js | 77 +-- .../documentation/theme/plugin.mjs | 67 -- .../typed-peripheral-digitalitems/mkdocs.yml | 65 ++ .../package-lock.json | 259 ------- .../package.json | 5 +- .../requirements-docs.txt | 1 + .../tsconfig.docs.json | 7 - .../typedoc.json | 87 --- projects/typescript-tests/package-lock.json | 1 - 21 files changed, 443 insertions(+), 1078 deletions(-) create mode 100644 projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs create mode 100644 projects/typed-peripheral-digitalitems/documentation/generate-docs.test.mjs delete mode 100644 projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs create mode 100644 projects/typed-peripheral-digitalitems/mkdocs.yml create mode 100644 projects/typed-peripheral-digitalitems/requirements-docs.txt delete mode 100644 projects/typed-peripheral-digitalitems/tsconfig.docs.json delete mode 100644 projects/typed-peripheral-digitalitems/typedoc.json diff --git a/.github/scripts/docs-site.mjs b/.github/scripts/docs-site.mjs index cc9a3f4..9965456 100644 --- a/.github/scripts/docs-site.mjs +++ b/.github/scripts/docs-site.mjs @@ -5,20 +5,21 @@ import { spawnSync } from "node:child_process"; import { dirname, isAbsolute, relative, resolve, sep } from "node:path"; import { fileURLToPath } from "node:url"; -export const REQUIRED_DOC_FILES = [ +export const COMMON_DOC_FILES = [ "projects/typed-peripheral-digitalitems/build.gradle.kts", "projects/typed-peripheral-digitalitems/package.json", "projects/typed-peripheral-digitalitems/package-lock.json", - "projects/typed-peripheral-digitalitems/typedoc.json", - "projects/typed-peripheral-digitalitems/tsconfig.docs.json", "projects/typed-peripheral-digitalitems/shared.ts", "projects/typed-peripheral-digitalitems/digitizer.ts", "projects/typed-peripheral-digitalitems/advanced_digitizer.ts", "projects/typed-peripheral-digitalitems/documentation/index.md", - "projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs", "projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css", "projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.js", ]; +const DOC_GENERATORS = [ + ["projects/typed-peripheral-digitalitems/typedoc.json", "projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs"], + ["projects/typed-peripheral-digitalitems/mkdocs.yml", "projects/typed-peripheral-digitalitems/requirements-docs.txt", "projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs"], +]; const BRANCHES = ["1.20", "1.21"]; const TAG_RE = /^v\d+(?:\.\d+)+(?:-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?$/; @@ -119,9 +120,10 @@ function show(repository, ref, path) { } function refHasDocs(repository, ref) { - return REQUIRED_DOC_FILES.every((path) => - git(repository, ["cat-file", "-e", `${ref}:${path}`], true).status === 0 - ) && /\bgenerateDocs\b/.test(show(repository, ref, REQUIRED_DOC_FILES[0])); + const has = (path) => git(repository, ["cat-file", "-e", `${ref}:${path}`], true).status === 0; + return COMMON_DOC_FILES.every(has) + && DOC_GENERATORS.some((files) => files.every(has)) + && /\bgenerateDocs\b/.test(show(repository, ref, COMMON_DOC_FILES[0])); } function metadata(repository, ref) { @@ -265,7 +267,10 @@ function localTarget(root, html, rawLink) { fail(`Malformed local link in ${html}: ${rawLink}`); } if (pathname.includes("\\") || pathname.includes("\0")) fail(`Unsafe local link in ${html}: ${rawLink}`); - const target = pathname.startsWith("/") ? resolve(root, `.${pathname}`) : resolve(dirname(html), pathname); + const versionedPath = pathname.match(/^\/.*?\/((?:branch|tag)\/.*)$/)?.[1]; + const target = pathname.startsWith("/") + ? resolve(root, versionedPath ?? `.${pathname}`) + : resolve(dirname(html), pathname); if (!within(root, target)) fail(`Local link escapes site in ${html}: ${rawLink}`); return target; } @@ -290,9 +295,6 @@ export async function validateSite(site) { for (const entry of plan) { const versionRoot = resolve(root, entry.path); await regularFile(resolve(versionRoot, "index.html"), `${entry.path} index.html`); - for (const asset of ["search.js", "navigation.js", "custom.css", "custom.js"]) { - await regularFile(resolve(versionRoot, "assets", asset), `${entry.path} ${asset}`); - } } const canonicalRoot = await realpath(root); for (const html of await htmlFiles(root)) { diff --git a/.github/scripts/docs-site.test.mjs b/.github/scripts/docs-site.test.mjs index 33f96cf..5e4a8f8 100644 --- a/.github/scripts/docs-site.test.mjs +++ b/.github/scripts/docs-site.test.mjs @@ -23,11 +23,11 @@ function entry(kind, name, sha = SHA_A) { }; } -async function fragment(root, path, html = 'asset') { +async function fragment(root, path, html = '') { const directory = join(root, path); await mkdir(join(directory, "assets"), { recursive: true }); await writeFile(join(directory, "index.html"), html); - for (const asset of ["main.js", "search.js", "navigation.js", "custom.css", "custom.js"]) { + for (const asset of ["main.js", "main.css"]) { await writeFile(join(directory, "assets", asset), "// docs\n"); } } @@ -107,14 +107,23 @@ test("validate rejects links escaping the site", async () => { await assert.rejects(validateSite(output), /escapes site/); }); -test("validate rejects missing TypeDoc assets", async () => { +test("validate resolves hosted version paths below the Pages project prefix", async () => { + const root = await mkdtemp(join(tmpdir(), "docs-site-prefix-")); + const input = join(root, "input"); + const output = join(root, "output"); + await fragment(input, "branch/1.20", ''); + await assembleSite({ input, output, plan: [entry("branch", "1.20")] }); + await validateSite(output); +}); + +test("validate rejects a missing linked asset", async () => { const root = await mkdtemp(join(tmpdir(), "docs-site-assets-")); const input = join(root, "input"); const output = join(root, "output"); await fragment(input, "branch/1.20"); await assembleSite({ input, output, plan: [entry("branch", "1.20")] }); - await unlink(join(output, "branch", "1.20", "assets", "search.js")); - await assert.rejects(validateSite(output), /Missing branch\/1.20 search.js/); + await unlink(join(output, "branch", "1.20", "assets", "main.css")); + await assert.rejects(validateSite(output), /Broken local link.*assets\/main.css/); }); test("assemble rejects a symlinked output ancestor", async () => { diff --git a/.github/workflows/docs-check.yml b/.github/workflows/docs-check.yml index a253169..90c0ea2 100644 --- a/.github/workflows/docs-check.yml +++ b/.github/workflows/docs-check.yml @@ -42,6 +42,16 @@ jobs: - name: Set up Gradle uses: gradle/actions/setup-gradle@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: projects/typed-peripheral-digitalitems/requirements-docs.txt + + - name: Install documentation dependencies + run: timeout --foreground 120s python -m pip install -r projects/typed-peripheral-digitalitems/requirements-docs.txt + - name: Build package and documentation run: timeout --foreground 600s ./gradlew :typed-peripheral-digitalitems:compileTypeScript :typed-peripheral-digitalitems:generateDocs --no-daemon @@ -55,7 +65,7 @@ jobs: run: | set -euo pipefail NODE="$PWD/.gradle/nodejs/node-v22.14.0-linux-x64/bin/node" - timeout --foreground 60s "$NODE" --test .github/scripts/docs-site.test.mjs + timeout --foreground 60s "$NODE" --test .github/scripts/docs-site.test.mjs projects/typed-peripheral-digitalitems/documentation/generate-docs.test.mjs mkdir -p build/docs-fragments/branch/1.20 cp -a projects/typed-peripheral-digitalitems/docs/. build/docs-fragments/branch/1.20/ timeout --foreground 60s "$NODE" .github/scripts/docs-site.mjs assemble --input build/docs-fragments --output build/docs-site --plan-json "$DOCS_PLAN" diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index ffc47e6..67cdb2c 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -67,6 +67,17 @@ jobs: - name: Set up Gradle uses: gradle/actions/setup-gradle@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install MkDocs for migrated refs + run: | + if [[ -f projects/typed-peripheral-digitalitems/requirements-docs.txt ]]; then + timeout --foreground 120s python -m pip install -r projects/typed-peripheral-digitalitems/requirements-docs.txt + fi + - name: Generate documentation with ref-owned task env: DOCS_PATH: ${{ matrix.path }} diff --git a/projects/typed-peripheral-digitalitems/.gitignore b/projects/typed-peripheral-digitalitems/.gitignore index b4504e2..dff745a 100644 --- a/projects/typed-peripheral-digitalitems/.gitignore +++ b/projects/typed-peripheral-digitalitems/.gitignore @@ -2,3 +2,4 @@ *.d.ts node_modules/ docs/ +.mkdocs-build/ diff --git a/projects/typed-peripheral-digitalitems/build.gradle.kts b/projects/typed-peripheral-digitalitems/build.gradle.kts index 0eaa4b2..ba09555 100644 --- a/projects/typed-peripheral-digitalitems/build.gradle.kts +++ b/projects/typed-peripheral-digitalitems/build.gradle.kts @@ -54,10 +54,11 @@ val generateDocs by tasks.registering(NpmTask::class) { "package.json", "package-lock.json", "tsconfig.json", - "tsconfig.docs.json", - "typedoc.json", + "mkdocs.yml", + "requirements-docs.txt", "*.ts", "documentation/**/*.md", + "documentation/**/*.mjs", "documentation/theme/**", ) exclude("node_modules/**", "*.d.ts") @@ -71,6 +72,7 @@ tasks.assemble { tasks.clean { delete(file("docs")) + delete(file(".mkdocs-build")) delete(fileTree(projectDir) { include("*.d.ts", "*.lua") }) diff --git a/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs b/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs new file mode 100644 index 0000000..8cbcc97 --- /dev/null +++ b/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs @@ -0,0 +1,196 @@ +import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import { spawnSync } from "node:child_process"; +import { dirname, relative, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import ts from "typescript"; + +const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const peripherals = [ + { interfaceName: "Digitizer", page: "guides/basic-digitizer.md" }, + { interfaceName: "AdvancedDigitizer", page: "guides/advanced-items.md" }, +]; + +function text(parts) { + return ts.displayPartsToString(parts).trim(); +} + +function tagsFor(signature, checker) { + const result = { params: new Map(), returns: "", throws: [] }; + for (const tag of signature.getJsDocTags(checker)) { + const value = text(tag.text); + if (tag.name === "param") { + const match = /^(\S+)\s*-?\s*(.*)$/s.exec(value); + if (match) result.params.set(match[1], match[2]); + } else if (tag.name === "returns" || tag.name === "return") result.returns = value; + else if (tag.name === "throws") result.throws.push(value); + } + return result; +} + +function sourceInterface(symbol) { + const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0]; + let current = declaration; + while (current && !ts.isInterfaceDeclaration(current)) current = current.parent; + return current?.name.text; +} + +export function extractPeripheral(program, interfaceName) { + const checker = program.getTypeChecker(); + const matches = []; + for (const sourceFile of program.getSourceFiles()) { + if (sourceFile.isDeclarationFile && sourceFile.fileName.includes("typescript/lib/")) continue; + ts.forEachChild(sourceFile, (node) => { + if (ts.isInterfaceDeclaration(node) && node.name.text === interfaceName) matches.push(node); + }); + } + if (matches.length !== 1) throw new Error(`Expected one ${interfaceName} interface, found ${matches.length}`); + const declaration = matches[0]; + let peripheralType = null; + for (const sourceFile of program.getSourceFiles()) { + const visit = (node) => { + if (ts.isNewExpression(node) + && node.typeArguments?.[0]?.getText() === interfaceName + && node.arguments?.[0] + && ts.isStringLiteral(node.arguments[0])) { + peripheralType = node.arguments[0].text; + } + ts.forEachChild(node, visit); + }; + ts.forEachChild(sourceFile, visit); + } + if (!peripheralType) throw new Error(`No peripheral provider found for ${interfaceName}`); + const type = checker.getTypeAtLocation(declaration); + const methods = checker.getPropertiesOfType(type).flatMap((symbol) => { + const methodType = checker.getTypeOfSymbolAtLocation(symbol, declaration); + const signatures = checker.getSignaturesOfType(methodType, ts.SignatureKind.Call); + if (!signatures.length) return []; + return [{ + name: symbol.name, + inheritedFrom: sourceInterface(symbol) === interfaceName ? null : sourceInterface(symbol), + signatures: signatures.map((signature) => { + const tags = tagsFor(signature, checker); + const parameters = signature.getParameters().map((parameter) => { + const parameterDeclaration = parameter.valueDeclaration ?? parameter.declarations?.[0] ?? declaration; + return { + name: parameter.name, + type: checker.typeToString(checker.getTypeOfSymbolAtLocation(parameter, parameterDeclaration), declaration, ts.TypeFormatFlags.NoTruncation), + optional: Boolean(parameter.flags & ts.SymbolFlags.Optional) || Boolean(parameterDeclaration.questionToken), + description: tags.params.get(parameter.name) ?? "", + }; + }); + return { + signature: `${symbol.name}${checker.signatureToString(signature, declaration, ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope)}`, + summary: text(signature.getDocumentationComment(checker)), + parameters, + returns: tags.returns, + throws: tags.throws, + }; + }), + }]; + }); + if (!methods.length) throw new Error(`${interfaceName} has no methods`); + return { interfaceName, peripheralType, methods }; +} + +function escapeCell(value) { + return value.replaceAll("|", "\\|").replaceAll("\n", " "); +} + +export function renderPeripheral(peripheral, revision = "") { + const lines = [ + "## Peripheral methods", + "", + "The reference below is generated from the published TypeScript interface.", + "", + ]; + for (const method of peripheral.methods) { + lines.push(`### \`${method.name}\``, ""); + if (method.inheritedFrom) lines.push(`*Inherited from \`${method.inheritedFrom}\`.*`, ""); + for (const [index, signature] of method.signatures.entries()) { + if (method.signatures.length > 1) lines.push(`#### Overload ${index + 1}`, ""); + lines.push("```typescript", signature.signature, "```", ""); + if (signature.summary) lines.push(signature.summary, ""); + if (signature.parameters.length) { + lines.push("| Parameter | Type | Description |", "| --- | --- | --- |"); + for (const parameter of signature.parameters) { + const name = `\`${parameter.name}${parameter.optional ? "?" : ""}\``; + lines.push(`| ${name} | \`${escapeCell(parameter.type)}\` | ${escapeCell(parameter.description)} |`); + } + lines.push(""); + } + if (signature.returns) lines.push(`**Returns:** ${signature.returns}`, ""); + for (const error of signature.throws) lines.push(`**Throws:** ${error}`, ""); + } + } + if (revision) lines.push(`[View TypeScript source](https://github.com/SirEdvin/DigitalItems/tree/${revision}/projects/typed-peripheral-digitalitems)`, ""); + return lines.join("\n"); +} + +function options(args) { + const result = { out: resolve(projectRoot, "docs"), revision: "", siteUrl: "" }; + for (let index = 0; index < args.length; index += 2) { + const value = args[index + 1]; + if (!value) throw new Error(`Missing value for ${args[index]}`); + if (args[index] === "--out") result.out = resolve(value); + else if (args[index] === "--gitRevision") result.revision = value; + else if (args[index] === "--hostedBaseUrl") result.siteUrl = value; + else throw new Error(`Unknown option: ${args[index]}`); + } + return result; +} + +export async function buildDocs(args = []) { + const config = options(args); + const buildRoot = resolve(projectRoot, ".mkdocs-build"); + await rm(buildRoot, { recursive: true, force: true }); + await cp(resolve(projectRoot, "documentation"), buildRoot, { + recursive: true, + filter: (path) => !path.endsWith("generate-docs.mjs") && !path.endsWith("generate-docs.test.mjs") && !path.includes("/theme/"), + }); + await mkdir(resolve(buildRoot, "assets/peripherals"), { recursive: true }); + const textureRoot = resolve(projectRoot, "../core/src/main/resources/assets/digitalitems/textures/block"); + await cp(resolve(textureRoot, "digitizer_front_on.png"), resolve(buildRoot, "assets/peripherals/digitizer.png")); + await cp(resolve(textureRoot, "advanced_digitizer_front_on.png"), resolve(buildRoot, "assets/peripherals/advanced_digitizer.png")); + await mkdir(resolve(buildRoot, "assets/stylesheets"), { recursive: true }); + await mkdir(resolve(buildRoot, "assets/javascripts"), { recursive: true }); + await cp(resolve(projectRoot, "documentation/theme/digitalitems.css"), resolve(buildRoot, "assets/stylesheets/digitalitems.css")); + await cp(resolve(projectRoot, "documentation/theme/digitalitems.js"), resolve(buildRoot, "assets/javascripts/digitalitems.js")); + await cp(resolve(projectRoot, "documentation/theme/favicon.svg"), resolve(buildRoot, "assets/favicon.svg")); + + const parsed = ts.parseJsonConfigFileContent( + ts.readConfigFile(resolve(projectRoot, "tsconfig.json"), ts.sys.readFile).config, + ts.sys, + projectRoot, + { noEmit: true, strictNullChecks: true }, + ); + const program = ts.createProgram(parsed.fileNames, parsed.options); + const errors = ts.getPreEmitDiagnostics(program); + if (errors.length) throw new Error(ts.formatDiagnosticsWithColorAndContext(errors, { + getCanonicalFileName: (name) => name, + getCurrentDirectory: () => projectRoot, + getNewLine: () => "\n", + })); + for (const peripheral of peripherals) { + const path = resolve(buildRoot, peripheral.page); + const markdown = await readFile(path, "utf8"); + const extracted = extractPeripheral(program, peripheral.interfaceName); + await writeFile(path, `${markdown.replaceAll("{{ peripheralType }}", extracted.peripheralType).trim()}\n\n${renderPeripheral(extracted, config.revision)}\n`); + } + + await rm(config.out, { recursive: true, force: true }); + const result = spawnSync("python3", ["-m", "mkdocs", "build", "--strict", "--clean", "--site-dir", config.out], { + cwd: projectRoot, + env: config.siteUrl ? { ...process.env, DOCS_SITE_URL: config.siteUrl } : process.env, + encoding: "utf8", + }); + if (result.status !== 0) throw new Error((result.stderr || result.stdout).trim()); + await rm(buildRoot, { recursive: true, force: true }); + return relative(projectRoot, config.out); +} + +if (resolve(process.argv[1] ?? "") === fileURLToPath(import.meta.url)) { + buildDocs(process.argv.slice(2)).catch((error) => { + process.stderr.write(`generate-docs: ${error.message}\n`); + process.exitCode = 1; + }); +} diff --git a/projects/typed-peripheral-digitalitems/documentation/generate-docs.test.mjs b/projects/typed-peripheral-digitalitems/documentation/generate-docs.test.mjs new file mode 100644 index 0000000..6c111e6 --- /dev/null +++ b/projects/typed-peripheral-digitalitems/documentation/generate-docs.test.mjs @@ -0,0 +1,34 @@ +import assert from "node:assert/strict"; +import { resolve } from "node:path"; +import test from "node:test"; +import ts from "typescript"; +import { extractPeripheral, renderPeripheral } from "./generate-docs.mjs"; + +const root = resolve(import.meta.dirname, ".."); +const parsed = ts.parseJsonConfigFileContent( + ts.readConfigFile(resolve(root, "tsconfig.json"), ts.sys.readFile).config, + ts.sys, + root, + { noEmit: true, strictNullChecks: true }, +); +const program = ts.createProgram(parsed.fileNames, parsed.options); + +test("extracts direct, inherited, and generic digitizer methods", () => { + const peripheral = extractPeripheral(program, "Digitizer"); + assert.equal(peripheral.peripheralType, "digitizer"); + assert.equal(peripheral.methods.length, 15); + assert.match(peripheral.methods.find(({ name }) => name === "getConfiguration").signatures[0].signature, /DigitizerConfiguration/); + assert.equal(peripheral.methods.find(({ name }) => name === "size").inheritedFrom, "InventoryViewAPI"); + assert.match(renderPeripheral(peripheral), /\*\*Throws:\*\* A Lua error if the slot is empty/); +}); + +test("preserves advanced digitizer overloads and nullable tuple unions", () => { + const peripheral = extractPeripheral(program, "AdvancedDigitizer"); + assert.equal(peripheral.peripheralType, "advanced_digitizer"); + assert.equal(peripheral.methods.length, 11); + assert.equal(peripheral.methods.find(({ name }) => name === "digitize").signatures.length, 2); + assert.equal(peripheral.methods.find(({ name }) => name === "get").signatures.length, 3); + const markdown = renderPeripheral(peripheral); + assert.match(markdown, /filter\?: number \| string \| LuaTable \| null/); + assert.match(markdown, /LuaMultiReturn<\[null, string\] \| \[number, null\]>/); +}); diff --git a/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md b/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md index 42faf03..00d5cd1 100644 --- a/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md +++ b/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md @@ -5,8 +5,8 @@ peripheralInterface: AdvancedDigitizer # Advanced Digitizer
-
Advanced digitizer block
-
MULTI-STORAGE PERIPHERAL

The advanced digitizer moves items, fluids, and energy between attached storage and durable digital identifiers.

Peripheral type
advanced_digitizer
Storage
Items, fluids, energy
Failure style
Recoverable result pairs
+
Advanced digitizer block
+
MULTI-STORAGE PERIPHERAL

The advanced digitizer moves items, fluids, and energy between attached storage and durable digital identifiers.

Peripheral type
{{ peripheralType }}
Storage
Items, fluids, energy
Failure style
Recoverable result pairs
The advanced digitizer can extract from its internal inventory with source diff --git a/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md b/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md index b3eb5c6..ad1a08a 100644 --- a/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md +++ b/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md @@ -5,8 +5,8 @@ peripheralInterface: Digitizer # Basic Digitizer
-
Digitizer block
-
ITEM PERIPHERAL

The basic digitizer turns physical item stacks into portable digital identifiers and restores them through its internal inventory.

Peripheral type
digitizer
Storage
Items
Failure style
Lua errors
+
Digitizer block
+
ITEM PERIPHERAL

The basic digitizer turns physical item stacks into portable digital identifiers and restores them through its internal inventory.

Peripheral type
{{ peripheralType }}
Storage
Items
Failure style
Lua errors
Slots are one-based and default to slot 1. `digitize()` removes the complete diff --git a/projects/typed-peripheral-digitalitems/documentation/index.md b/projects/typed-peripheral-digitalitems/documentation/index.md index c3dd707..a216b0d 100644 --- a/projects/typed-peripheral-digitalitems/documentation/index.md +++ b/projects/typed-peripheral-digitalitems/documentation/index.md @@ -7,12 +7,12 @@ TypeScript with TypeScriptToLua. ## Choose your digitizer diff --git a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css index bba38b1..5b4ff27 100644 --- a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css +++ b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css @@ -1,271 +1,17 @@ -@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=Manrope:wght@400;600;700;800&display=swap"); :root { - --di-accent: #4f46e5; - --di-accent-hover: #4338ca; - --di-accent-dark: #3730a3; - --di-accent-subtle: rgba(79, 70, 229, 0.06); - --di-bg: #ffffff; - --di-bg-secondary: #f8fafc; - --di-bg-elevated: #eef2ff; - --di-bg-code: #f1f5f9; - --di-border: rgba(79, 70, 229, 0.1); - --di-border-subtle: rgba(148, 163, 184, 0.12); - --di-text-primary: #0f172a; - --di-text-secondary: #475569; - --di-text-muted: #94a3b8; - --di-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04); - --di-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.06); - --color-background: var(--di-bg); - --color-background-secondary: var(--di-bg-secondary); - --color-background-active: var(--di-bg-elevated); - --color-accent: var(--di-accent); - --color-text: var(--di-text-primary); - --color-text-aside: var(--di-text-secondary); - --color-link: #4f46e5; - --color-ts-keyword: #c084fc; - --color-ts-string: #34d399; - --color-ts-number: #fb923c; + --md-text-font: "Manrope"; + --md-code-font: "IBM Plex Mono"; + --di-line: color-mix(in srgb, var(--md-primary-fg-color) 28%, transparent); + --di-panel: color-mix(in srgb, var(--md-primary-fg-color) 7%, var(--md-default-bg-color)); + --di-warm: #e29a32; } -html[data-theme="dark"] { - --di-accent: #6366f1; - --di-accent-hover: #818cf8; - --di-accent-dark: #4f46e5; - --di-accent-subtle: rgba(99, 102, 241, 0.08); - --di-bg: #0c0e16; - --di-bg-secondary: #141622; - --di-bg-elevated: #1a1d2e; - --di-bg-code: #111322; - --di-border: rgba(99, 102, 241, 0.12); - --di-border-subtle: rgba(148, 163, 184, 0.08); - --di-text-primary: #e2e8f0; - --di-text-secondary: #8892b0; - --di-text-muted: #5a6380; - --di-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.12); - --di-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.18); - --color-text: var(--di-text-primary); - --color-link: #818cf8; -} - -@media (prefers-color-scheme: dark) { - html[data-theme="os"] { - --di-accent: #6366f1; - --di-accent-hover: #818cf8; - --di-accent-dark: #4f46e5; - --di-accent-subtle: rgba(99, 102, 241, 0.08); - --di-bg: #0c0e16; - --di-bg-secondary: #141622; - --di-bg-elevated: #1a1d2e; - --di-bg-code: #111322; - --di-border: rgba(99, 102, 241, 0.12); - --di-border-subtle: rgba(148, 163, 184, 0.08); - --di-text-primary: #e2e8f0; - --di-text-secondary: #8892b0; - --di-text-muted: #5a6380; - --di-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.12); - --di-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.18); - --color-text: var(--di-text-primary); - --color-link: #818cf8; - } -} - -body { - font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - line-height: 1.6; - background-color: var(--di-bg); -} - -.container-main { - backdrop-filter: none; -} - -.col-content { - position: relative; - max-width: 920px; -} - -.col-content::before { - content: none; - display: none; -} - -h1, h2, h3, h4, h5, h6, -.site-menu, -.tsd-page-title { - font-family: "Inter", system-ui, -apple-system, sans-serif; - letter-spacing: -0.025em; -} - -h1 { - font-weight: 800; - font-size: 2rem; - line-height: 1.25; - text-wrap: balance; - margin-bottom: 1rem; -} - -h2 { - font-weight: 700; - font-size: 1.5rem; - line-height: 1.3; - border-bottom: 1px solid var(--di-border); - padding-bottom: 0.5rem; - margin-top: 2.5rem; - margin-bottom: 1rem; -} - -h3 { - font-weight: 600; - font-size: 1.25rem; - line-height: 1.4; - margin-top: 1.75rem; - margin-bottom: 0.75rem; -} - -h4 { - font-weight: 600; - font-size: 1.1rem; - margin-top: 1.5rem; - margin-bottom: 0.5rem; -} - -p { - margin-bottom: 1rem; -} - -ul, ol { - margin-bottom: 1rem; - padding-left: 1.5rem; -} - -li { - margin-bottom: 0.35rem; -} - -.tsd-panel { - border: 1px solid var(--di-border); - border-radius: 10px; - box-shadow: var(--di-shadow-sm); - background: var(--di-bg-secondary); -} - -.tsd-member { - border: 1px solid var(--di-border); - border-radius: 10px; - box-shadow: var(--di-shadow-sm); -} - -.tsd-signature, -code, -pre { - font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", Consolas, monospace; -} - -code { - background: var(--di-bg-code); - border: 1px solid var(--di-border-subtle); - border-radius: 5px; - padding: 0.1em 0.4em; - font-size: 0.875em; - color: var(--di-accent-hover); -} - -html[data-theme="light"] code { - color: var(--di-accent); -} - -pre { - background: var(--di-bg-code); - border: 1px solid var(--di-border); - border-radius: 10px; - padding: 1rem 1.25rem; - overflow-x: auto; - box-shadow: var(--di-shadow-sm); -} - -pre code { - background: none; - border: none; - padding: 0; - font-size: 0.85rem; - line-height: 1.65; - color: var(--di-text-primary); -} - -a { - color: var(--color-link); - text-decoration: none; - text-underline-offset: 0.2em; - transition: color 0.15s ease; -} - -a:hover { - color: var(--di-accent-hover); -} - -table { - border: 1px solid var(--di-border); - border-radius: 10px; - overflow: hidden; - box-shadow: var(--di-shadow-sm); -} - -th, td { - border-color: var(--di-border-subtle); -} - -.tsd-page-toolbar { - background: var(--di-bg); - border-bottom: 1px solid var(--di-border); -} - -.tsd-page-toolbar .title { - font-family: "Inter", system-ui, sans-serif; - font-weight: 700; - color: var(--di-text-primary); -} - -.di-signal-line { - display: none !important; -} - -.col-content p, -.col-content li { - max-width: 78ch; -} - -.col-content table { - width: 100%; - border-collapse: collapse; - overflow: hidden; -} - -.col-content th { - color: var(--di-accent-hover); - background: var(--di-bg-elevated); - text-align: left; -} - -.col-content th, -.col-content td { - padding: 0.8rem 1rem; - border-bottom: 1px solid var(--di-border-subtle); - vertical-align: top; -} - -.col-content blockquote { - margin: 1.5rem 0; - padding: 0.8rem 1.1rem; - border-left: 4px solid var(--color-ts-number); - background: color-mix(in srgb, var(--color-ts-number) 7%, var(--di-bg-secondary)); -} - -.col-content blockquote p { - margin: 0; -} +.md-grid { max-width: 78rem; } +.md-typeset h1 { font-weight: 800; letter-spacing: -.04em; } +.md-typeset h2 { margin-top: 2.5em; padding-bottom: .35em; border-bottom: 1px solid var(--di-line); } +.md-typeset h3 code { font-size: .9em; color: var(--md-accent-fg-color); } .di-peripheral-grid { display: grid; @@ -276,329 +22,72 @@ th, td { .di-peripheral-card, .di-peripheral-hero { - border: 1px solid var(--di-border); - background: linear-gradient(145deg, var(--di-bg-elevated), var(--di-bg-secondary)); + border: 1px solid var(--di-line); + background: linear-gradient(145deg, var(--di-panel), var(--md-default-bg-color)); + box-shadow: 0 14px 35px rgba(0, 0, 0, .08); } .di-peripheral-card { display: flex; gap: 1.2rem; - min-height: 190px; - padding: 1.25rem; - border-radius: 10px; - color: var(--color-text); - text-decoration: none; - transition: border-color 160ms ease, transform 160ms ease; + min-height: 11rem; + padding: 1.2rem; + border-radius: .55rem; + color: var(--md-default-fg-color) !important; + transition: transform 160ms ease, border-color 160ms ease; } -.di-peripheral-card:hover { - border-color: var(--di-accent-hover); - color: var(--color-text); - transform: translateY(-3px); -} - -.di-peripheral-card--advanced, -.di-peripheral-hero--advanced { - border-color: color-mix(in srgb, var(--color-ts-number) 35%, transparent); -} +.di-peripheral-card:hover { transform: translateY(-3px); border-color: var(--md-accent-fg-color); } +.di-peripheral-card--advanced, .di-peripheral-hero--advanced { border-color: color-mix(in srgb, var(--di-warm) 45%, transparent); } .di-peripheral-image { display: grid; - flex: 0 0 128px; - min-height: 128px; + flex: 0 0 7rem; + min-height: 7rem; place-items: center; - border-radius: 6px; - background-color: #0a1114; - background-image: linear-gradient(45deg, rgba(255,255,255,.035) 25%, transparent 25%, transparent 75%, rgba(255,255,255,.035) 75%), linear-gradient(45deg, rgba(255,255,255,.035) 25%, transparent 25%, transparent 75%, rgba(255,255,255,.035) 75%); - background-position: 0 0, 8px 8px; - background-size: 16px 16px; + border-radius: .35rem; + background: #091114 repeating-conic-gradient(rgba(255,255,255,.035) 0 25%, transparent 0 50%) 0 / 16px 16px; } -.di-peripheral-image img { - width: 112px; - height: 112px; - image-rendering: pixelated; -} - -.di-peripheral-copy { - display: flex; - flex-direction: column; -} - -.di-peripheral-copy strong { - font-size: 1.25rem; -} - -.di-peripheral-copy small { - margin: 0.25rem 0 0.75rem; - color: var(--di-text-secondary); -} - -.di-peripheral-copy b { - margin-top: auto; - color: var(--di-accent-hover); - font-size: 0.82rem; -} +.di-peripheral-image img { width: 6rem; height: 6rem; image-rendering: pixelated; } +.di-peripheral-copy { display: flex; flex-direction: column; } +.di-peripheral-copy strong { font-size: 1.15rem; } +.di-peripheral-copy small { margin: .25rem 0 .65rem; opacity: .72; } +.di-peripheral-copy b { margin-top: auto; color: var(--md-accent-fg-color); font-size: .78rem; } .di-peripheral-hero { display: grid; - grid-template-columns: 176px minmax(0, 1fr); - gap: 1.75rem; + grid-template-columns: 10rem minmax(0, 1fr); + gap: 1.5rem; margin: 1rem 0 2rem; - padding: 1.5rem; - border-radius: 10px; -} - -.di-peripheral-hero .di-peripheral-image { - min-height: 176px; -} - -.di-peripheral-hero .di-peripheral-image img { - width: 144px; - height: 144px; -} - -.di-eyebrow { - color: var(--color-ts-number); - font: 700 0.7rem/1 "Lucida Console", Monaco, monospace; - letter-spacing: 0.12em; -} - -.di-peripheral-hero p { - margin: 0.55rem 0 1rem; - font-size: 1.05rem; -} - -.di-peripheral-hero dl { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 0.7rem; - margin: 0; -} - -.di-peripheral-hero dl div { - min-width: 0; -} - -.di-peripheral-hero dt { - color: var(--di-text-secondary); - font-size: 0.68rem; - text-transform: uppercase; - letter-spacing: 0.06em; -} - -.di-peripheral-hero dd { - margin: 0.2rem 0 0; - overflow-wrap: anywhere; - font-weight: 650; -} - -.di-method-reference { - margin-top: 3rem; -} - -.di-generated-note { - margin-bottom: 1.25rem; - color: var(--di-text-secondary); - font-size: 0.9rem; -} - -.di-method-card { - margin: 1rem 0; - border: 1px solid var(--di-border); - border-radius: 8px; - background: var(--di-bg-secondary); - box-shadow: 0 8px 22px rgba(0, 0, 0, 0.1); - overflow: hidden; -} - -.di-method-card > h3 { - margin: 0; - padding: 0.75rem 1rem; - border-bottom: 1px solid var(--di-border); - background: var(--di-bg-elevated); - color: var(--di-accent-hover); - font-size: 1rem; -} - -.di-method-overload { - padding: 1rem; -} - -.di-method-overload + .di-method-overload { - border-top: 1px dashed var(--di-border); -} - -.di-method-overload .tsd-signature { - margin: 0 0 1rem; - padding: 0.8rem; - box-shadow: none; - overflow-x: auto; -} - -.di-method-overload .tsd-comment { - margin: 0.75rem 0; -} - -.di-method-overload h4 { - margin: 1rem 0 0.35rem; - color: var(--di-text-secondary); - font-size: 0.78rem; - letter-spacing: 0.05em; - text-transform: uppercase; -} - -.di-version-panel { - margin: 0.5rem 0 1rem; - padding: 0.6rem 0.75rem; - border: 1px solid var(--di-border); - border-radius: 8px; - background: var(--di-bg-secondary); -} - -.di-version-label { - display: block; - margin-bottom: 0.3rem; - color: var(--di-accent); - font: 600 0.68rem/1.2 "Inter", system-ui, sans-serif; - letter-spacing: 0.05em; - text-transform: uppercase; -} - -#di-version-select { - width: 100%; - padding: 0.35rem 0.5rem; - border: 1px solid var(--di-border); - border-radius: 6px; - color: var(--di-text-primary); - background: var(--di-bg); - font-family: "Inter", system-ui, sans-serif; - font-size: 0.82rem; -} - -#di-version-select:focus-visible { - outline: 2px solid var(--di-accent); - outline-offset: 1px; -} - -.tsd-typography { - line-height: 1.7; -} - -.tsd-anchor-link { - color: var(--di-accent); -} - -.tsd-anchor-link:hover { - color: var(--di-accent-hover); -} - -.tsd-kind-icon { - color: var(--di-accent); -} - -.tsd-navigation a, -.tsd-nav-link { - font-family: "Inter", system-ui, sans-serif; -} - -.tsd-accordion-summary { - font-family: "Inter", system-ui, sans-serif; -} - -.tsd-filter-visibility .settings-label { - font-family: "Inter", system-ui, sans-serif; - font-weight: 600; - font-size: 0.8rem; - letter-spacing: 0.03em; - text-transform: uppercase; - color: var(--di-text-secondary); -} - -#tsd-theme { - font-family: "Inter", system-ui, sans-serif; - border: 1px solid var(--di-border); - border-radius: 6px; - padding: 0.3rem 0.5rem; - background: var(--di-bg); - color: var(--di-text-primary); - font-size: 0.82rem; -} - -.tsd-search-input { - font-family: "Inter", system-ui, sans-serif; - border: 1px solid var(--di-border); - border-radius: 8px; -} - -.tsd-generator { - border-top: 1px solid var(--di-border); - padding-top: 1rem; - margin-top: 2rem; - color: var(--di-text-muted); - font-size: 0.82rem; -} - -footer { - border-top: 1px solid var(--di-border); -} - -@media (prefers-reduced-motion: no-preference) { - .col-content > * { - animation: di-fade-in 180ms ease-out both; - } - - @keyframes di-fade-in { - from { - opacity: 0; - transform: translateY(3px); - } - } - - a, .tsd-panel, .tsd-member { - transition: all 0.15s ease; - } -} - -@media (max-width: 769px) { - h1 { font-size: 1.5rem; } - h2 { font-size: 1.25rem; } - h3 { font-size: 1.1rem; } - - .tsd-signature, - pre, - table { - max-width: 100%; - overflow-x: auto; - } - - .di-peripheral-grid { - grid-template-columns: 1fr; - } - - .di-peripheral-hero { - grid-template-columns: 1fr; - } - - .di-peripheral-hero .di-peripheral-image { - min-height: 144px; - } - - .di-peripheral-hero dl { - grid-template-columns: 1fr 1fr; - } -} - -@media (max-width: 480px) { - .di-peripheral-card { - flex-direction: column; - } - - .di-peripheral-image { - flex-basis: auto; - } - - .di-peripheral-hero dl { - grid-template-columns: 1fr; - } + padding: 1.4rem; + border-radius: .55rem; +} + +.di-peripheral-hero .di-peripheral-image { min-height: 10rem; } +.di-peripheral-hero .di-peripheral-image img { width: 8rem; height: 8rem; } +.di-eyebrow { color: var(--di-warm); font: 600 .66rem/1 "IBM Plex Mono"; letter-spacing: .12em; } +.di-peripheral-hero p { margin: .55rem 0 1rem; font-size: .95rem; } +.di-peripheral-hero dl { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .7rem; margin: 0; } +.di-peripheral-hero dt { opacity: .65; font-size: .62rem; text-transform: uppercase; letter-spacing: .06em; } +.di-peripheral-hero dd { margin: .15rem 0 0; overflow-wrap: anywhere; font-weight: 700; } + +.md-typeset h2#peripheral-methods ~ h3 { + margin: 1.4rem 0 0; + padding: .75rem 1rem; + border: 1px solid var(--di-line); + border-bottom: 0; + border-radius: .45rem .45rem 0 0; + background: var(--di-panel); +} + +.md-typeset table:not([class]) { display: table; width: 100%; } +.md-typeset table:not([class]) th { background: var(--di-panel); } +#di-version-select { max-width: 15rem; padding: .28rem .5rem; border: 1px solid var(--di-line); border-radius: .25rem; color: inherit; background: var(--md-default-bg-color); font: 600 .65rem "IBM Plex Mono"; } + +@media (max-width: 760px) { + .di-peripheral-grid { grid-template-columns: 1fr; } + .di-peripheral-hero { grid-template-columns: 1fr; } + .di-peripheral-hero dl { grid-template-columns: 1fr; } + .di-peripheral-hero .di-peripheral-image { min-height: 9rem; } } diff --git a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.js b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.js index 5e11b2e..9e1f407 100644 --- a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.js +++ b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.js @@ -1,28 +1,18 @@ -(() => { - const themeSelector = document.querySelector("#tsd-theme"); - if (themeSelector instanceof HTMLSelectElement) { - const systemOption = themeSelector.querySelector('option[value="os"]'); - if (systemOption) systemOption.textContent = "System"; - } - - const selector = document.querySelector("#di-version-select"); - if (!(selector instanceof HTMLSelectElement)) return; - - const script = document.currentScript ?? [...document.scripts].find((entry) => - entry.src.endsWith("/custom.js") - ); +document.addEventListener("DOMContentLoaded", () => { + const script = [...document.scripts].find((entry) => entry.src.endsWith("/assets/javascripts/digitalitems.js")); if (!script) return; - const scriptUrl = new URL(script.src); - const versionRoot = scriptUrl.pathname.match( - /^(.*\/)(?:branch|tag)\/[^/]+\/assets\/custom\.js$/ - ); - if (!versionRoot) return; - - const siteRoot = new URL(versionRoot[1], scriptUrl.origin); - const currentPath = decodeURIComponent( - scriptUrl.pathname.slice(versionRoot[1].length).replace(/assets\/custom\.js$/, "") - ); + const match = scriptUrl.pathname.match(/^(.*\/)((?:branch|tag)\/[^/]+)\/assets\/javascripts\/digitalitems\.js$/); + if (!match) return; + + const siteRoot = new URL(match[1], scriptUrl.origin); + const currentVersion = `${decodeURIComponent(match[2])}/`; + const select = document.createElement("select"); + select.id = "di-version-select"; + select.ariaLabel = "Documentation version"; + select.disabled = true; + select.append(new Option("Loading versions...")); + document.querySelector(".md-header__inner")?.append(select); fetch(new URL("versions.json", siteRoot)) .then((response) => { @@ -30,38 +20,15 @@ return response.json(); }) .then((manifest) => { - if (!manifest || !Array.isArray(manifest.versions)) { - throw new Error("Unsupported version manifest"); - } - - selector.replaceChildren(); - const groups = new Map([ - ["branch", document.createElement("optgroup")], - ["tag", document.createElement("optgroup")], - ]); - groups.get("branch").label = "Development branches"; - groups.get("tag").label = "Releases"; - - for (const entry of manifest.versions) { - if (!entry || !groups.has(entry.kind) || typeof entry.path !== "string") continue; - const option = document.createElement("option"); - option.value = entry.path; - option.textContent = entry.label || entry.name; - option.selected = entry.path === currentPath; - groups.get(entry.kind).append(option); - } - - for (const group of groups.values()) { - if (group.children.length > 0) selector.append(group); + select.replaceChildren(); + for (const entry of manifest.versions ?? []) { + const option = new Option(entry.label || entry.name, entry.path); + option.selected = entry.path === currentVersion; + select.append(option); } - selector.disabled = selector.options.length === 0; + select.disabled = select.options.length === 0; }) - .catch(() => { - selector.options[0].textContent = "Version list unavailable"; - }); + .catch(() => { select.options[0].textContent = "Versions unavailable"; }); - selector.addEventListener("change", () => { - if (!selector.value) return; - window.location.assign(new URL(selector.value, siteRoot)); - }); -})(); + select.addEventListener("change", () => window.location.assign(new URL(select.value, siteRoot))); +}); diff --git a/projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs b/projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs deleted file mode 100644 index 294aa81..0000000 --- a/projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs +++ /dev/null @@ -1,67 +0,0 @@ -import { JSX, ReflectionKind } from "typedoc"; - -function renderPeripheralMethods(context) { - if (!context.model.isDocument()) return; - - const interfaceName = context.model.frontmatter.peripheralInterface; - if (typeof interfaceName !== "string") return; - - const peripheral = context.page.project - .getReflectionsByKind(ReflectionKind.Interface) - .find((reflection) => reflection.name === interfaceName); - if (!peripheral?.children) return; - - const methods = peripheral.children.filter( - (reflection) => reflection.kindOf(ReflectionKind.Method) && reflection.signatures?.length - ); - if (methods.length === 0) return; - - context.page.pageHeadings.push({ - link: "#peripheral-methods", - text: "Peripheral methods", - level: 2, - }); - - return JSX.createElement( - "section", - { class: "di-method-reference", "aria-labelledby": "peripheral-methods" }, - JSX.createElement("h2", { id: "peripheral-methods" }, "Peripheral methods"), - JSX.createElement( - "p", - { class: "di-generated-note" }, - "Generated from the TypeScript interface. Signatures, parameters, return values, and errors stay synchronized with the published API." - ), - ...methods.map((method) => - JSX.createElement( - "article", - { class: "di-method-card" }, - JSX.createElement("h3", null, method.name), - ...method.signatures.map((signature) => - JSX.createElement( - "div", - { class: "di-method-overload" }, - context.memberSignatureTitle(signature), - context.memberSignatureBody(signature, { hideSources: true }) - ) - ) - ) - ) - ); -} - -export function load(app) { - app.renderer.hooks.on("sidebar.begin", () => - JSX.createElement( - "section", - { class: "di-version-panel", "aria-label": "Documentation version" }, - JSX.createElement("span", { class: "di-version-label" }, "Version"), - JSX.createElement( - "select", - { id: "di-version-select", disabled: true }, - JSX.createElement("option", null, "Local build") - ) - ) - ); - - app.renderer.hooks.on("content.end", renderPeripheralMethods); -} diff --git a/projects/typed-peripheral-digitalitems/mkdocs.yml b/projects/typed-peripheral-digitalitems/mkdocs.yml new file mode 100644 index 0000000..6c1f4a6 --- /dev/null +++ b/projects/typed-peripheral-digitalitems/mkdocs.yml @@ -0,0 +1,65 @@ +site_name: Digital Items Peripheral API +site_description: TypeScript and Lua reference for Digital Items peripherals +site_url: !ENV [DOCS_SITE_URL, "https://siredvin.github.io/DigitalItems/branch/1.20/"] +repo_url: https://github.com/SirEdvin/DigitalItems +repo_name: SirEdvin/DigitalItems +docs_dir: .mkdocs-build +site_dir: docs +strict: true + +theme: + name: material + logo: assets/favicon.svg + favicon: assets/favicon.svg + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: teal + accent: amber + toggle: + icon: material/weather-night + name: Use dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: teal + accent: amber + toggle: + icon: material/weather-sunny + name: Use light mode + features: + - content.code.copy + - navigation.footer + - navigation.indexes + - navigation.sections + - navigation.top + - search.highlight + +nav: + - Overview: index.md + - Getting started: getting-started.md + - Digital identifiers: digital-identifiers.md + - Peripherals: + - Basic digitizer: guides/basic-digitizer.md + - Advanced digitizer: guides/advanced-items.md + - Guides: + - Fluids and energy: guides/fluids-and-energy.md + +markdown_extensions: + - admonition + - attr_list + - md_in_html + - pymdownx.details + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.superfences + - toc: + permalink: true + +extra_css: + - assets/stylesheets/digitalitems.css +extra_javascript: + - assets/javascripts/digitalitems.js +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/SirEdvin/DigitalItems diff --git a/projects/typed-peripheral-digitalitems/package-lock.json b/projects/typed-peripheral-digitalitems/package-lock.json index 5cac6cb..82e3e95 100644 --- a/projects/typed-peripheral-digitalitems/package-lock.json +++ b/projects/typed-peripheral-digitalitems/package-lock.json @@ -17,80 +17,16 @@ "@siredvin/typed-peripheral-base": "*" }, "devDependencies": { - "typedoc": "0.28.20", "typescript": "5.7.2", "typescript-to-lua": "1.29.1" } }, - "node_modules/@gerrit0/mini-shiki": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.23.0.tgz", - "integrity": "sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/engine-oniguruma": "^3.23.0", - "@shikijs/langs": "^3.23.0", - "@shikijs/themes": "^3.23.0", - "@shikijs/types": "^3.23.0", - "@shikijs/vscode-textmate": "^10.0.2" - } - }, "node_modules/@jackmacwindows/lua-types": { "version": "2.13.2", "resolved": "https://registry.npmjs.org/@jackmacwindows/lua-types/-/lua-types-2.13.2.tgz", "integrity": "sha512-XoqHmgtG1SLWzmMx8wQc0sJtPmrzjCFbiwNiBIZE7HOEXWdJi7NDKVlE1nl9A5745Yb5jIkfz2deOzMtHiHVcQ==", "license": "MIT" }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", - "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.23.0", - "@shikijs/vscode-textmate": "^10.0.2" - } - }, - "node_modules/@shikijs/langs": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", - "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.23.0" - } - }, - "node_modules/@shikijs/themes": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", - "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.23.0" - } - }, - "node_modules/@shikijs/types": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", - "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, - "node_modules/@shikijs/vscode-textmate": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", - "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", - "dev": true, - "license": "MIT" - }, "node_modules/@siredvin/api-types": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@siredvin/api-types/-/api-types-1.1.0.tgz", @@ -134,23 +70,6 @@ "@siredvin/craftos-types": "1.2.0" } }, - "node_modules/@types/hast": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", - "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@typescript-to-lua/language-extensions": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/@typescript-to-lua/language-extensions/-/language-extensions-1.19.0.tgz", @@ -158,36 +77,6 @@ "dev": true, "license": "MIT" }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/enhanced-resolve": { "version": "5.8.2", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz", @@ -202,19 +91,6 @@ "node": ">=10.13.0" } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", @@ -271,84 +147,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/linkify-it": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", - "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/markdown-it" - } - ], - "license": "MIT", - "dependencies": { - "uc.micro": "^2.0.0" - } - }, - "node_modules/lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/markdown-it": { - "version": "14.3.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz", - "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/markdown-it" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.5.0", - "linkify-it": "^5.0.2", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "dev": true, - "license": "MIT" - }, - "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -369,16 +167,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/resolve": { "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", @@ -438,30 +226,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/typedoc": { - "version": "0.28.20", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.20.tgz", - "integrity": "sha512-uSKqkh8Cr48vllnEy+jdaAgOeR6Y+QCBW7usgUsKj7gJEfR7stw9U/fE49LBnj2tPRKPY0c0EBJSWe9Appmplg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@gerrit0/mini-shiki": "^3.23.0", - "lunr": "^2.3.9", - "markdown-it": "^14.3.0", - "minimatch": "^10.2.5", - "yaml": "^2.9.0" - }, - "bin": { - "typedoc": "bin/typedoc" - }, - "engines": { - "node": ">= 18", - "pnpm": ">= 10" - }, - "peerDependencies": { - "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x || 6.0.x" - } - }, "node_modules/typescript": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", @@ -498,29 +262,6 @@ "peerDependencies": { "typescript": "5.7.2" } - }, - "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } } } } diff --git a/projects/typed-peripheral-digitalitems/package.json b/projects/typed-peripheral-digitalitems/package.json index 0a8ab07..1741f11 100644 --- a/projects/typed-peripheral-digitalitems/package.json +++ b/projects/typed-peripheral-digitalitems/package.json @@ -14,8 +14,8 @@ "scripts": { "build": "tstl", "clean": "rm -f *.lua *.d.ts", - "docs": "typedoc", - "docs:watch": "typedoc --watch" + "docs": "node documentation/generate-docs.mjs", + "docs:test": "node --test documentation/generate-docs.test.mjs" }, "dependencies": { "@jackmacwindows/lua-types": "^2.13.1", @@ -26,7 +26,6 @@ "@siredvin/typed-peripheral-base": "*" }, "devDependencies": { - "typedoc": "0.28.20", "typescript": "5.7.2", "typescript-to-lua": "1.29.1" } diff --git a/projects/typed-peripheral-digitalitems/requirements-docs.txt b/projects/typed-peripheral-digitalitems/requirements-docs.txt new file mode 100644 index 0000000..c8de0c5 --- /dev/null +++ b/projects/typed-peripheral-digitalitems/requirements-docs.txt @@ -0,0 +1 @@ +mkdocs-material==9.6.14 diff --git a/projects/typed-peripheral-digitalitems/tsconfig.docs.json b/projects/typed-peripheral-digitalitems/tsconfig.docs.json deleted file mode 100644 index 35b63ea..0000000 --- a/projects/typed-peripheral-digitalitems/tsconfig.docs.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "skipLibCheck": true - }, - "include": ["./*.ts"] -} diff --git a/projects/typed-peripheral-digitalitems/typedoc.json b/projects/typed-peripheral-digitalitems/typedoc.json deleted file mode 100644 index 9a3b9c5..0000000 --- a/projects/typed-peripheral-digitalitems/typedoc.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "$schema": "https://typedoc.org/schema.json", - "entryPoints": ["./shared.ts", "./digitizer.ts", "./advanced_digitizer.ts"], - "tsconfig": "./tsconfig.docs.json", - "out": "./docs", - "name": "@siredvin/typed-peripheral-digitalitems", - "readme": "./documentation/index.md", - "projectDocuments": [ - "./documentation/getting-started.md", - "./documentation/digital-identifiers.md", - "./documentation/guides/*.md" - ], - "plugin": ["./documentation/theme/plugin.mjs"], - "customCss": "./documentation/theme/digitalitems.css", - "customJs": "./documentation/theme/digitalitems.js", - "favicon": "./documentation/theme/favicon.svg", - "lightHighlightTheme": "material-theme-lighter", - "darkHighlightTheme": "material-theme-darker", - "highlightLanguages": ["typescript", "lua", "json", "bash"], - "excludeExternals": true, - "basePath": "../..", - "sourceLinkTemplate": "https://github.com/SirEdvin/DigitalItems/blob/{gitRevision}/{path}#L{line}", - "navigationLinks": { - "GitHub": "https://github.com/SirEdvin/DigitalItems" - }, - "blockTags": [ - "@defaultValue", - "@deprecated", - "@example", - "@jsx", - "@param", - "@privateRemarks", - "@remarks", - "@returns", - "@see", - "@throws", - "@typeParam", - "@author", - "@callback", - "@category", - "@categoryDescription", - "@default", - "@document", - "@extends", - "@augments", - "@yields", - "@group", - "@groupDescription", - "@import", - "@inheritDoc", - "@license", - "@module", - "@mergeModuleWith", - "@prop", - "@property", - "@return", - "@satisfies", - "@since", - "@sortStrategy", - "@template", - "@this", - "@type", - "@typedef", - "@summary", - "@preventInline", - "@inlineType", - "@preventExpand", - "@expandType", - "@noSelf" - ], - "excludeTags": [ - "@override", - "@virtual", - "@privateRemarks", - "@satisfies", - "@overload", - "@inline", - "@inlineType", - "@noSelf" - ], - "validation": { - "notDocumented": true, - "invalidLink": true, - "notExported": true - }, - "treatWarningsAsErrors": true -} diff --git a/projects/typescript-tests/package-lock.json b/projects/typescript-tests/package-lock.json index 9eae9f0..fa73958 100644 --- a/projects/typescript-tests/package-lock.json +++ b/projects/typescript-tests/package-lock.json @@ -35,7 +35,6 @@ "@siredvin/typed-peripheral-base": "*" }, "devDependencies": { - "typedoc": "^0.28.16", "typescript": "5.7.2", "typescript-to-lua": "1.29.1" } From fc7a57a9119f567699b6a6d031d2c30639be8293 Mon Sep 17 00:00:00 2001 From: SirEdvin Date: Wed, 22 Jul 2026 19:11:43 +0000 Subject: [PATCH 06/10] docs: render isometric peripheral blocks --- .../documentation/generate-docs.mjs | 24 +++++++++++++------ .../documentation/generate-docs.test.mjs | 5 +++- .../documentation/guides/advanced-items.md | 2 +- .../documentation/guides/basic-digitizer.md | 2 +- .../documentation/index.md | 4 ++-- 5 files changed, 25 insertions(+), 12 deletions(-) diff --git a/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs b/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs index 8cbcc97..455963a 100644 --- a/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs +++ b/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs @@ -96,6 +96,17 @@ function escapeCell(value) { return value.replaceAll("|", "\\|").replaceAll("\n", " "); } +async function renderBlock(textureRoot, block) { + const texture = async (face) => (await readFile(resolve(textureRoot, `${block}_${face}.png`))).toString("base64"); + const [side, front, top] = await Promise.all([texture("side"), texture("front_on"), texture("top")]); + return ` + + + + +\n`; +} + export function renderPeripheral(peripheral, revision = "") { const lines = [ "## Peripheral methods", @@ -104,11 +115,9 @@ export function renderPeripheral(peripheral, revision = "") { "", ]; for (const method of peripheral.methods) { - lines.push(`### \`${method.name}\``, ""); - if (method.inheritedFrom) lines.push(`*Inherited from \`${method.inheritedFrom}\`.*`, ""); - for (const [index, signature] of method.signatures.entries()) { - if (method.signatures.length > 1) lines.push(`#### Overload ${index + 1}`, ""); - lines.push("```typescript", signature.signature, "```", ""); + for (const signature of method.signatures) { + lines.push(`### \`${signature.signature}\``, ""); + if (method.inheritedFrom) lines.push(`*Inherited from \`${method.inheritedFrom}\`.*`, ""); if (signature.summary) lines.push(signature.summary, ""); if (signature.parameters.length) { lines.push("| Parameter | Type | Description |", "| --- | --- | --- |"); @@ -149,8 +158,9 @@ export async function buildDocs(args = []) { }); await mkdir(resolve(buildRoot, "assets/peripherals"), { recursive: true }); const textureRoot = resolve(projectRoot, "../core/src/main/resources/assets/digitalitems/textures/block"); - await cp(resolve(textureRoot, "digitizer_front_on.png"), resolve(buildRoot, "assets/peripherals/digitizer.png")); - await cp(resolve(textureRoot, "advanced_digitizer_front_on.png"), resolve(buildRoot, "assets/peripherals/advanced_digitizer.png")); + for (const block of ["digitizer", "advanced_digitizer"]) { + await writeFile(resolve(buildRoot, "assets/peripherals", `${block}.svg`), await renderBlock(textureRoot, block)); + } await mkdir(resolve(buildRoot, "assets/stylesheets"), { recursive: true }); await mkdir(resolve(buildRoot, "assets/javascripts"), { recursive: true }); await cp(resolve(projectRoot, "documentation/theme/digitalitems.css"), resolve(buildRoot, "assets/stylesheets/digitalitems.css")); diff --git a/projects/typed-peripheral-digitalitems/documentation/generate-docs.test.mjs b/projects/typed-peripheral-digitalitems/documentation/generate-docs.test.mjs index 6c111e6..17c66db 100644 --- a/projects/typed-peripheral-digitalitems/documentation/generate-docs.test.mjs +++ b/projects/typed-peripheral-digitalitems/documentation/generate-docs.test.mjs @@ -19,7 +19,10 @@ test("extracts direct, inherited, and generic digitizer methods", () => { assert.equal(peripheral.methods.length, 15); assert.match(peripheral.methods.find(({ name }) => name === "getConfiguration").signatures[0].signature, /DigitizerConfiguration/); assert.equal(peripheral.methods.find(({ name }) => name === "size").inheritedFrom, "InventoryViewAPI"); - assert.match(renderPeripheral(peripheral), /\*\*Throws:\*\* A Lua error if the slot is empty/); + const markdown = renderPeripheral(peripheral); + assert.match(markdown, /\*\*Throws:\*\* A Lua error if the slot is empty/); + assert.match(markdown, /### `getConfiguration\(\): DigitizerConfiguration`/); + assert.doesNotMatch(markdown, /```typescript/); }); test("preserves advanced digitizer overloads and nullable tuple unions", () => { diff --git a/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md b/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md index 00d5cd1..58cecc4 100644 --- a/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md +++ b/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md @@ -5,7 +5,7 @@ peripheralInterface: AdvancedDigitizer # Advanced Digitizer
-
Advanced digitizer block
+
Advanced digitizer block
MULTI-STORAGE PERIPHERAL

The advanced digitizer moves items, fluids, and energy between attached storage and durable digital identifiers.

Peripheral type
{{ peripheralType }}
Storage
Items, fluids, energy
Failure style
Recoverable result pairs
diff --git a/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md b/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md index ad1a08a..4aa1636 100644 --- a/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md +++ b/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md @@ -5,7 +5,7 @@ peripheralInterface: Digitizer # Basic Digitizer
-
Digitizer block
+
Digitizer block
ITEM PERIPHERAL

The basic digitizer turns physical item stacks into portable digital identifiers and restores them through its internal inventory.

Peripheral type
{{ peripheralType }}
Storage
Items
Failure style
Lua errors
diff --git a/projects/typed-peripheral-digitalitems/documentation/index.md b/projects/typed-peripheral-digitalitems/documentation/index.md index a216b0d..13ce6da 100644 --- a/projects/typed-peripheral-digitalitems/documentation/index.md +++ b/projects/typed-peripheral-digitalitems/documentation/index.md @@ -8,11 +8,11 @@ TypeScript with TypeScriptToLua. From d03c6a18c170c8bd8ab9567943347a854584c741 Mon Sep 17 00:00:00 2001 From: SirEdvin Date: Wed, 22 Jul 2026 19:19:08 +0000 Subject: [PATCH 07/10] docs: expand repository guidance --- AGENTS.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f1b39dd..92a0eb7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,17 +1,73 @@ -# Repository Guidelines +# Digital Items + +## Overview + +Digital Items is a Minecraft 1.20.1 mod that adds digital item storage peripherals for CC:Tweaked. It supports both Fabric and Forge from a shared core module. + +## Tech Stack + +- Runtime: Minecraft 1.20.1 / Java 17 +- Languages: Kotlin 2.0, Java, TypeScript, and Lua +- Loaders: Fabric Loader 0.15 and Forge 47 +- Computer mod: CC:Tweaked 1.113 +- Testing: Testiarium GameTests and TypeScript-to-Lua fixtures +- Build system: Gradle Wrapper + +## Key Commands + +Log handling is important. Always use an explicit timeout and silently save complete output. + +- Build: `mkdir -p build; LOG="build/gradle-$(date +%Y%m%d-%H%M%S).log"; timeout --foreground 10m ./gradlew build --no-daemon >"$LOG" 2>&1` +- GameTests: `mkdir -p build; LOG="build/gametests-$(date +%Y%m%d-%H%M%S).log"; timeout --foreground 20m xvfb-run -a ./gradlew gameTest --no-daemon -PminimalTestEnvironment >"$LOG" 2>&1` +- TypeScript fixtures: `mkdir -p build; LOG="build/typescript-tests-$(date +%Y%m%d-%H%M%S).log"; timeout --foreground 5m ./gradlew :typescript-tests:compileTestLua --no-daemon >"$LOG" 2>&1` + +Increase timeouts only when required. Stop development clients and servers after collecting results. Report the command, exit code, duration, log path, and relevant errors; inspect only the relevant failure window. ## Project Structure -DigitalItems is a Gradle multi-project repository. Subprojects live under `projects/`: +```text +projects/ + core/ # Shared implementation, resources, and GameTests + fabric/ # Fabric integration and test mod + forge/ # Forge integration and test mod + typed-peripheral-digitalitems/ # Publishable TypeScriptToLua peripheral API + typescript-tests/ # TypeScript sources compiled to ComputerCraft Lua fixtures +gradle/ # Version catalog and Gradle configuration +``` -- `core`: loader-independent Minecraft mod code and shared test-mod sources. -- `forge`: Forge implementation, generated resources, and Forge GameTests. -- `fabric`: Fabric implementation, generated resources, and Fabric GameTests. -- `typed-peripheral-digitalitems`: publishable TypeScriptToLua peripheral API package. -- `typescript-tests`: TypeScript GameTest programs compiled to Lua and included in the shared test mod. +Production code lives in `src/main/`. Shared GameTests and fixtures live in `projects/core/src/testMod/`; loader test-mod entry points and metadata live in each loader's `src/testMod/`. The TypeScript tests consume `typed-peripheral-digitalitems` through a local npm file dependency. Gradle compiles the package before installing the test project's npm dependencies. ## Contribution Workflow Every new task must be implemented on a dedicated branch and submitted as a GitHub pull request. Do not commit task changes directly to the base branch. + +## Conventions + +- Keep loader-independent behavior in `projects/core/`. +- Keep loader API usage in the corresponding `fabric` or `forge` module. +- Follow the official Kotlin code style configured in `gradle.properties`. +- Reuse existing project patterns before adding helpers, abstractions, or dependencies. +- Comments explain why, not what. + +## DO NOT MODIFY + +- Never edit generated build output; change its source and rerun the relevant Gradle task. +- Never commit `build/`, development run directories, logs, EULA files, or `node_modules/`. +- Do not modify unrelated user changes in a dirty worktree. + +## Testing Approach + +- Run `:typescript-tests:compileTestLua` after changing TypeScript fixtures or the typed peripheral package. +- Run the root `gameTest` task for both Fabric and Forge GameTests, using `xvfb-run` for graphics-dependent Minecraft startup. +- Run the timed multi-loader build before marking code changes complete. +- Fix failing tests rather than skipping them. + +## Code Style + +- Prefer the smallest correct change. +- Do not add speculative abstractions or dependencies. +- Keep shared and loader-specific responsibilities separated. +- Preserve validation, error handling, and dedicated-server safety. +- If requirements are unclear, ask instead of assuming. From cbcf6715ab754fa58ccbfb9f8d42f11b1a2d59b3 Mon Sep 17 00:00:00 2001 From: SirEdvin Date: Wed, 22 Jul 2026 19:26:35 +0000 Subject: [PATCH 08/10] docs: fix block images and method headings --- .../documentation/generate-docs.mjs | 13 +------------ .../documentation/guides/advanced-items.md | 2 +- .../documentation/guides/basic-digitizer.md | 2 +- .../documentation/index.md | 4 ++-- .../documentation/theme/digitalitems.css | 9 --------- 5 files changed, 5 insertions(+), 25 deletions(-) diff --git a/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs b/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs index 455963a..d48e576 100644 --- a/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs +++ b/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs @@ -96,17 +96,6 @@ function escapeCell(value) { return value.replaceAll("|", "\\|").replaceAll("\n", " "); } -async function renderBlock(textureRoot, block) { - const texture = async (face) => (await readFile(resolve(textureRoot, `${block}_${face}.png`))).toString("base64"); - const [side, front, top] = await Promise.all([texture("side"), texture("front_on"), texture("top")]); - return ` - - - - -\n`; -} - export function renderPeripheral(peripheral, revision = "") { const lines = [ "## Peripheral methods", @@ -159,7 +148,7 @@ export async function buildDocs(args = []) { await mkdir(resolve(buildRoot, "assets/peripherals"), { recursive: true }); const textureRoot = resolve(projectRoot, "../core/src/main/resources/assets/digitalitems/textures/block"); for (const block of ["digitizer", "advanced_digitizer"]) { - await writeFile(resolve(buildRoot, "assets/peripherals", `${block}.svg`), await renderBlock(textureRoot, block)); + await cp(resolve(textureRoot, `${block}_front_on.png`), resolve(buildRoot, "assets/peripherals", `${block}.png`)); } await mkdir(resolve(buildRoot, "assets/stylesheets"), { recursive: true }); await mkdir(resolve(buildRoot, "assets/javascripts"), { recursive: true }); diff --git a/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md b/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md index 58cecc4..00d5cd1 100644 --- a/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md +++ b/projects/typed-peripheral-digitalitems/documentation/guides/advanced-items.md @@ -5,7 +5,7 @@ peripheralInterface: AdvancedDigitizer # Advanced Digitizer
-
Advanced digitizer block
+
Advanced digitizer block
MULTI-STORAGE PERIPHERAL

The advanced digitizer moves items, fluids, and energy between attached storage and durable digital identifiers.

Peripheral type
{{ peripheralType }}
Storage
Items, fluids, energy
Failure style
Recoverable result pairs
diff --git a/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md b/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md index 4aa1636..ad1a08a 100644 --- a/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md +++ b/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md @@ -5,7 +5,7 @@ peripheralInterface: Digitizer # Basic Digitizer
-
Digitizer block
+
Digitizer block
ITEM PERIPHERAL

The basic digitizer turns physical item stacks into portable digital identifiers and restores them through its internal inventory.

Peripheral type
{{ peripheralType }}
Storage
Items
Failure style
Lua errors
diff --git a/projects/typed-peripheral-digitalitems/documentation/index.md b/projects/typed-peripheral-digitalitems/documentation/index.md index 13ce6da..a216b0d 100644 --- a/projects/typed-peripheral-digitalitems/documentation/index.md +++ b/projects/typed-peripheral-digitalitems/documentation/index.md @@ -8,11 +8,11 @@ TypeScript with TypeScriptToLua. diff --git a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css index 5b4ff27..729a032 100644 --- a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css +++ b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css @@ -72,15 +72,6 @@ .di-peripheral-hero dt { opacity: .65; font-size: .62rem; text-transform: uppercase; letter-spacing: .06em; } .di-peripheral-hero dd { margin: .15rem 0 0; overflow-wrap: anywhere; font-weight: 700; } -.md-typeset h2#peripheral-methods ~ h3 { - margin: 1.4rem 0 0; - padding: .75rem 1rem; - border: 1px solid var(--di-line); - border-bottom: 0; - border-radius: .45rem .45rem 0 0; - background: var(--di-panel); -} - .md-typeset table:not([class]) { display: table; width: 100%; } .md-typeset table:not([class]) th { background: var(--di-panel); } #di-version-select { max-width: 15rem; padding: .28rem .5rem; border: 1px solid var(--di-line); border-radius: .25rem; color: inherit; background: var(--md-default-bg-color); font: 600 .65rem "IBM Plex Mono"; } From 4903e5f8ea729d6fdad5086e997925ff0791f235 Mon Sep 17 00:00:00 2001 From: SirEdvin Date: Wed, 22 Jul 2026 19:38:37 +0000 Subject: [PATCH 09/10] docs: render pixel-accurate block previews --- .../documentation/generate-docs.mjs | 101 +++++++++++++++++- .../documentation/generate-docs.test.mjs | 24 ++++- .../documentation/guides/advanced-items.md | 2 +- .../documentation/guides/basic-digitizer.md | 2 +- .../documentation/index.md | 4 +- 5 files changed, 122 insertions(+), 11 deletions(-) diff --git a/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs b/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs index d48e576..b2b60ad 100644 --- a/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs +++ b/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs @@ -2,6 +2,7 @@ import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; import { spawnSync } from "node:child_process"; import { dirname, relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; +import { inflateSync } from "node:zlib"; import ts from "typescript"; const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); @@ -96,6 +97,102 @@ function escapeCell(value) { return value.replaceAll("|", "\\|").replaceAll("\n", " "); } +function paeth(left, above, upperLeft) { + const estimate = left + above - upperLeft; + const leftDistance = Math.abs(estimate - left); + const aboveDistance = Math.abs(estimate - above); + const upperLeftDistance = Math.abs(estimate - upperLeft); + return leftDistance <= aboveDistance && leftDistance <= upperLeftDistance ? left : aboveDistance <= upperLeftDistance ? above : upperLeft; +} + +function decodePng(data) { + let width; + let height; + let channels; + const compressed = []; + for (let offset = 8; offset < data.length;) { + const length = data.readUInt32BE(offset); + const type = data.toString("ascii", offset + 4, offset + 8); + const chunk = data.subarray(offset + 8, offset + 8 + length); + if (type === "IHDR") { + width = chunk.readUInt32BE(0); + height = chunk.readUInt32BE(4); + // ponytail: decode only the PNG formats used by Minecraft textures; expand if an asset requires it. + if (chunk[8] !== 8 || chunk[12] !== 0 || ![2, 6].includes(chunk[9])) throw new Error("Unsupported block texture PNG"); + channels = chunk[9] === 6 ? 4 : 3; + } else if (type === "IDAT") compressed.push(chunk); + offset += length + 12; + } + if (!width || !height || !channels) throw new Error("Invalid block texture PNG"); + + const raw = inflateSync(Buffer.concat(compressed)); + const stride = width * channels; + const pixels = []; + let previous = Buffer.alloc(stride); + let offset = 0; + for (let y = 0; y < height; y++) { + const filter = raw[offset++]; + const row = Buffer.from(raw.subarray(offset, offset + stride)); + offset += stride; + for (let index = 0; index < stride; index++) { + const left = index >= channels ? row[index - channels] : 0; + const above = previous[index]; + const upperLeft = index >= channels ? previous[index - channels] : 0; + const predictor = filter === 0 ? 0 + : filter === 1 ? left + : filter === 2 ? above + : filter === 3 ? Math.floor((left + above) / 2) + : filter === 4 ? paeth(left, above, upperLeft) + : NaN; + if (Number.isNaN(predictor)) throw new Error(`Unsupported PNG filter ${filter}`); + row[index] = (row[index] + predictor) & 0xff; + } + for (let x = 0; x < width; x++) { + const index = x * channels; + pixels.push([row[index], row[index + 1], row[index + 2], channels === 4 ? row[index + 3] : 255]); + } + previous = row; + } + return { width, height, pixels }; +} + +function renderFace(texture, shade, project) { + if (texture.width !== 16 || texture.height !== 16) throw new Error("Block textures must be 16x16"); + const paths = []; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const [red, green, blue, alpha] = texture.pixels[y * 16 + x]; + if (alpha === 0) continue; + const color = [red, green, blue].map((channel) => Math.round(channel * shade).toString(16).padStart(2, "0")).join(""); + const points = [project(x, y), project(x + 1, y), project(x + 1, y + 1), project(x, y + 1)]; + paths.push(` `); + } + } + return paths.join("\n"); +} + +export async function renderBlock(textureRoot, block) { + const texture = async (face) => decodePng(await readFile(resolve(textureRoot, `${block}_${face}.png`))); + const [side, front, top] = await Promise.all([texture("side"), texture("front_on"), texture("top")]); + const sideFace = renderFace(side, 0.72, (x, y) => [16 + x * 3, 32 + x * 1.5 + y * 3]); + const frontFace = renderFace(front, 0.9, (x, y) => [64 + x * 3, 56 - x * 1.5 + y * 3]); + const topFace = renderFace(top, 1, (x, y) => [64 + (x - y) * 3, 8 + (x + y) * 1.5]); + return ` + +${sideFace} +${frontFace} +${topFace} +\n`; +} + +function methodHeading(method, signature) { + const literalType = /^"[^"]+"(?: \| "[^"]+")*$/; + const parameters = signature.parameters.map((parameter, index) => ( + method.signatures.length > 1 && index === 0 && literalType.test(parameter.type) ? parameter.type : parameter.name + )); + return `${method.name}(${parameters.join(", ")})`; +} + export function renderPeripheral(peripheral, revision = "") { const lines = [ "## Peripheral methods", @@ -105,7 +202,7 @@ export function renderPeripheral(peripheral, revision = "") { ]; for (const method of peripheral.methods) { for (const signature of method.signatures) { - lines.push(`### \`${signature.signature}\``, ""); + lines.push(`### \`${methodHeading(method, signature)}\``, ""); if (method.inheritedFrom) lines.push(`*Inherited from \`${method.inheritedFrom}\`.*`, ""); if (signature.summary) lines.push(signature.summary, ""); if (signature.parameters.length) { @@ -148,7 +245,7 @@ export async function buildDocs(args = []) { await mkdir(resolve(buildRoot, "assets/peripherals"), { recursive: true }); const textureRoot = resolve(projectRoot, "../core/src/main/resources/assets/digitalitems/textures/block"); for (const block of ["digitizer", "advanced_digitizer"]) { - await cp(resolve(textureRoot, `${block}_front_on.png`), resolve(buildRoot, "assets/peripherals", `${block}.png`)); + await writeFile(resolve(buildRoot, "assets/peripherals", `${block}.svg`), await renderBlock(textureRoot, block)); } await mkdir(resolve(buildRoot, "assets/stylesheets"), { recursive: true }); await mkdir(resolve(buildRoot, "assets/javascripts"), { recursive: true }); diff --git a/projects/typed-peripheral-digitalitems/documentation/generate-docs.test.mjs b/projects/typed-peripheral-digitalitems/documentation/generate-docs.test.mjs index 17c66db..9dfdbf9 100644 --- a/projects/typed-peripheral-digitalitems/documentation/generate-docs.test.mjs +++ b/projects/typed-peripheral-digitalitems/documentation/generate-docs.test.mjs @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { resolve } from "node:path"; import test from "node:test"; import ts from "typescript"; -import { extractPeripheral, renderPeripheral } from "./generate-docs.mjs"; +import { extractPeripheral, renderBlock, renderPeripheral } from "./generate-docs.mjs"; const root = resolve(import.meta.dirname, ".."); const parsed = ts.parseJsonConfigFileContent( @@ -21,7 +21,9 @@ test("extracts direct, inherited, and generic digitizer methods", () => { assert.equal(peripheral.methods.find(({ name }) => name === "size").inheritedFrom, "InventoryViewAPI"); const markdown = renderPeripheral(peripheral); assert.match(markdown, /\*\*Throws:\*\* A Lua error if the slot is empty/); - assert.match(markdown, /### `getConfiguration\(\): DigitizerConfiguration`/); + assert.match(markdown, /### `getConfiguration\(\)`/); + assert.match(markdown, /### `digitize\(slot\)`/); + assert.doesNotMatch(markdown, /### `[^`]*: [^`]*`/); assert.doesNotMatch(markdown, /```typescript/); }); @@ -29,9 +31,21 @@ test("preserves advanced digitizer overloads and nullable tuple unions", () => { const peripheral = extractPeripheral(program, "AdvancedDigitizer"); assert.equal(peripheral.peripheralType, "advanced_digitizer"); assert.equal(peripheral.methods.length, 11); - assert.equal(peripheral.methods.find(({ name }) => name === "digitize").signatures.length, 2); + const digitize = peripheral.methods.find(({ name }) => name === "digitize"); + assert.equal(digitize.signatures.length, 2); assert.equal(peripheral.methods.find(({ name }) => name === "get").signatures.length, 3); + assert.match(digitize.signatures[0].signature, /filter\?: number \| string \| LuaTable \| null/); + assert.match(digitize.signatures[0].signature, /LuaMultiReturn<\[null, string\] \| \[string, null\]>/); + assert.match(peripheral.methods.find(({ name }) => name === "rematerialize").signatures[0].signature, /LuaMultiReturn<\[null, string\] \| \[number, null\]>/); const markdown = renderPeripheral(peripheral); - assert.match(markdown, /filter\?: number \| string \| LuaTable \| null/); - assert.match(markdown, /LuaMultiReturn<\[null, string\] \| \[number, null\]>/); + assert.match(markdown, /### `digitize\("item", source, filter, limit, destination\)`/); + assert.match(markdown, /### `digitize\("fluid" \| "energy", source, filter, limit, destination\)`/); +}); + +test("renders all 16x16 block texture pixels as crisp SVG faces", async () => { + const textureRoot = resolve(root, "../core/src/main/resources/assets/digitalitems/textures/block"); + const svg = await renderBlock(textureRoot, "digitizer"); + assert.match(svg, /shape-rendering="crispEdges"/); + assert.equal(svg.match(/ -
Advanced digitizer block
+
Advanced digitizer block
MULTI-STORAGE PERIPHERAL

The advanced digitizer moves items, fluids, and energy between attached storage and durable digital identifiers.

Peripheral type
{{ peripheralType }}
Storage
Items, fluids, energy
Failure style
Recoverable result pairs
diff --git a/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md b/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md index ad1a08a..4aa1636 100644 --- a/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md +++ b/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md @@ -5,7 +5,7 @@ peripheralInterface: Digitizer # Basic Digitizer
-
Digitizer block
+
Digitizer block
ITEM PERIPHERAL

The basic digitizer turns physical item stacks into portable digital identifiers and restores them through its internal inventory.

Peripheral type
{{ peripheralType }}
Storage
Items
Failure style
Lua errors
diff --git a/projects/typed-peripheral-digitalitems/documentation/index.md b/projects/typed-peripheral-digitalitems/documentation/index.md index a216b0d..13ce6da 100644 --- a/projects/typed-peripheral-digitalitems/documentation/index.md +++ b/projects/typed-peripheral-digitalitems/documentation/index.md @@ -8,11 +8,11 @@ TypeScript with TypeScriptToLua. From 37a087d51a55b6ef329d8d1eca68cff1cf076c76 Mon Sep 17 00:00:00 2001 From: SirEdvin Date: Mon, 27 Jul 2026 09:03:00 +0000 Subject: [PATCH 10/10] Add venv --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index efa81af..2999dd3 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,5 @@ run/ .env UnlimitedPeripheralWorks-* .kotlin + +.venv \ No newline at end of file