token.sheet: Linked or independent token inspector
Read the token’s own sheet context, so an independent snapshot is never replaced by actor data.
What you will build
Read the token’s own sheet context, so an independent snapshot is never replaced by actor data.
Surface: token.sheet · Mode: augment · Gravewright 0.3.0 / SDK 1.x.
When it mounts and who can use it
Open a token sheet in the current scene. context.actorId, tokenId and sceneId identify the mount. tokens.get uses the mounted scene; actors.sheet receives tokenId explicitly.
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.
token-sheet/
manifest.json
main.mjs
styles.css
LICENSE.txtmanifest.json
{
"id": "example.surface-token-sheet",
"name": "Linked or independent token inspector",
"version": "1.0.0",
"sdk": {
"requires": ">=1.0.0 <2.0.0",
"tested": "1.0.0"
},
"description": "Read the token’s own sheet context, so an independent snapshot is never replaced by actor data.",
"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": {
"Link mode": "Link mode",
"Sheet revision": "Sheet revision",
"Refresh": "Refresh",
"Loading…": "Loading…",
"Error: {code}": "Error: {code}",
"Linked or independent token inspector": "Linked or independent token inspector"
},
"pt-BR": {
"Link mode": "Modo de vínculo",
"Sheet revision": "Revisão da ficha",
"Refresh": "Atualizar",
"Loading…": "Carregando…",
"Error: {code}": "Erro: {code}",
"Linked or independent token inspector": "Inspetor de token vinculado ou independente"
}
});
},
register({ register }) {
register("token.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("Linked or independent token 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 Promise.all([api.tokens.get(context.tokenId, {}, { signal }), api.actors.sheet(context.actorId, { tokenId: context.tokenId }, { signal })]);
if (signal.aborted || current !== ticket) return;
const [token, sheet] = value; output.textContent = `${sheet.name} · ${t("Link mode")}: ${token.linkMode} · ${t("Sheet revision")}: ${sheet.sheetVersion}`;
} 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("tokens.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
Read the token’s own sheet context, so an independent snapshot is never replaced by actor data.
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
Place one linked token and one independent token, edit their native sheets separately, and compare names, linkMode and revisions.
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 token.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