item.sheet: Item data inspector
Show the actual item name, type, version and system-owned data as safe JSON below its native editor.
What you will build
Show the actual item name, type, version and system-owned data as safe JSON below its native editor.
Surface: item.sheet · Mode: augment · Gravewright 0.3.0 / SDK 1.x.
When it mounts and who can use it
Open an item sheet. context.itemId selects an item in the authorized collection. A listed item may still deny opening.
The owner installs the local ZIP under Modules → Install module → Install from ZIP. The GM then activates the exact version in the table extension list. This example requests no extra permissions or global handle; all reads use the mount’s supplied api. GMs and players receive only server-authorized data. Streamers do not receive this authenticated mount.
Download the complete package
Download source ZIP · manifest.json · main.mjs · styles.css · LICENSE.txt
Save all four files in the same folder. ZIP the folder contents, with manifest.json directly at the archive root. Do not include a wrapper folder, local state or private keys. Local upload is the owner’s trust decision; for catalog distribution, use the signing workflow in the module reference.
item-sheet/
manifest.json
main.mjs
styles.css
LICENSE.txtmanifest.json
{
"id": "example.surface-item-sheet",
"name": "Item data inspector",
"version": "1.0.0",
"sdk": {
"requires": ">=1.0.0 <2.0.0",
"tested": "1.0.0"
},
"description": "Show the actual item name, type, version and system-owned data as safe JSON below its native editor.",
"author": "Gravewright documentation",
"license": "MIT",
"entry": "main.mjs"
}main.mjs
// Original documentation example. License: MIT. Target: Gravewright 0.3.0, SDK 1.x.
export default {
start({ api, styles }) {
styles.use("styles.css");
api.i18n.register({
"en": {
"Refresh": "Refresh",
"Loading…": "Loading…",
"Error: {code}": "Error: {code}",
"Item data inspector": "Item data inspector"
},
"pt-BR": {
"Refresh": "Atualizar",
"Loading…": "Carregando…",
"Error: {code}": "Erro: {code}",
"Item data inspector": "Inspetor de dados do item"
}
});
},
register({ register }) {
register("item.sheet", { mode: "augment", mount: this.mount });
},
async mount({ root, context, api, storage, viewport, events, signal, onDispose }) {
const el = (tag, text) => {
const node = root.ownerDocument.createElement(tag);
if (text !== undefined) node.textContent = text;
return node;
};
const t = (key, params) => api.i18n.t(key, params);
const panel = el("section"); panel.className = "gw-example";
const heading = el("h3", t("Item data inspector"));
const output = el("pre"); output.setAttribute("role", "status");
panel.append(heading, output); root.append(panel);
onDispose(() => panel.remove());
const report = error => {
if (!signal.aborted) output.textContent = t("Error: {code}", { code: error.code ?? "unavailable" });
};
let ticket = 0;
const refresh = async () => {
const current = ++ticket;
try {
const value = await api.items.get(context.itemId, {}, { signal });
if (signal.aborted || current !== ticket) return;
if (!value.canOpen) throw Object.assign(new Error("permission_denied"), { code: "permission_denied" }); output.textContent = JSON.stringify({ name: value.name, type: value.type, version: value.version, data: value.data }, null, 2);
} catch (error) { if (current === ticket) report(error); }
};
const refreshButton = el("button", t("Refresh")); refreshButton.type = "button";
panel.append(refreshButton);
refreshButton.addEventListener("click", () => void refresh(), { signal });
onDispose(api.events.on("items.changed", () => void refresh()));
await refresh();
},
stop() {
// Mount and module lifetimes already dispose styles, catalogs and subscriptions.
}
};styles.css
.gw-example { box-sizing: border-box; max-width: 640px; padding: var(--space-3); margin: var(--space-2); border: 1px solid var(--border); border-radius: var(--radius-md); color: var(--text); background: var(--surface); font: 14px/1.5 system-ui, sans-serif; }
.gw-example h3 { font-size: 16px; margin: var(--space-0) var(--space-0) var(--space-2); }
.gw-example pre { white-space: pre-wrap; overflow-wrap: anywhere; font: inherit; }
.gw-example button, .gw-example select, .gw-example input { color: inherit; background: var(--surface); font: inherit; padding: var(--space-1) var(--space-2); margin: var(--space-1); border: 1px solid var(--border); border-radius: var(--radius-sm); }
.gw-example button:focus-visible, .gw-example input:focus-visible, .gw-example select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.gw-example--compact { padding: var(--space-1); }
.gw-example-center { position: absolute; transform: translate(-50%, -50%); pointer-events: none; color: var(--text); background: var(--surface); padding: var(--space-1); border-radius: var(--radius-sm); font: 14px/1.5 system-ui, sans-serif; }Expected result and cleanup
Show the actual item name, type, version and system-owned data as safe JSON below its native editor.
The native interface remains usable. Closing a sheet, switching scenes, leaving the table or deactivating the module aborts the mount. Listeners use signal; events and owned elements are released through onDispose. Async reads check cancellation and discard old responses before rendering. Errors are displayed through error.code, including network failures.
Validate on a disposable table
Save a native item edit and check the new version and data. Use a string containing HTML and confirm it is displayed as text.
Open/close three times, then deactivate while a read or modal is pending. Check that no old panel or error appears after disposal.
Test empty state, long document names, keyboard navigation and a narrow viewport. Compare GM and player accounts.
Check the browser console. These source examples have runtime contract tests; installation and visual behavior still require validation on the target host.
How to use AI for this surface
Adapt the item.sheet example for [concrete goal] on Gravewright 0.3.0.
Read registry.json, this surface’s context schema, frontend.json and the native services used.
Keep start/register/stop, mount-scoped api, signal, onDispose and native modals.
Provide the complete manifest and files. Do not use app.shell/inside.content in packages.
Do not invent payload fields, core RPG rules, permissions or APIs.
Test player/GM, empty data, abort, repeated mounting and this example’s behavior.
Explain limitations and which checks actually ran.Continue
SDK · API · Examples by kind