@runmu.sh/ext-scene 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.0
4
+
5
+ - Alt+R (*Go to scene*) is now the extension's own command, `focus.scene`, in Settings → Keys under Scene: Focus. On μClient with extension API 1.14 it focuses the Scene panel when it is open; a rebinding you made keeps working. On an older μClient the client's own Alt+R stays in place.
6
+ - The context menus on exits and room items use the kinds `scene.exit` (`data: { exit }`) and `scene.item` (`data: { item }`), which the extension registers with a schema. On a μClient before API 1.14 it keeps publishing `scene-exit` and `scene-item`.
7
+ - `src/types.ts` types the two kinds for other extensions (`ContextKinds`, `SceneItemData`, `SceneExitData`), and the module exports `ITEM_KIND`, `EXIT_KIND` and `KIND_SCHEMAS`.
8
+ - Built against SDK 1.14. Still needs extension API 1.12 or later.
9
+
3
10
  ## 1.1.0
4
11
 
5
12
  - Present now comes before Exits, and a room with no exits shows no Exits heading at all, as on Underspire.
package/README.md CHANGED
@@ -22,13 +22,13 @@ Package names are case-insensitive. Entering a new room clears Present. The room
22
22
  ## What it looks like
23
23
  On `--bg-elev`: the title in uppercase `--accent-bright` with a glow and a `--border-bright` rule under it, the area in small faint capitals, the atmosphere in italic `--fg-dim`, the description, and the pose line in italic with a 2px left rule. Then `┤PRESENT├` over a `▸` list of the people and room items present (a hostile item carries a small `hostile` tag in `--alert`; nobody reads `none`), and, when the room has exits, `┤EXITS├` over a `▸` list. Each exit is a button: clicking it sends the direction as if typed. Before any room arrives the panel says NO ROOM YET (an upright, uppercase, tracked faint label).
24
24
 
25
- The panel is the region `scene`, so **Alt+R** (*Go to scene*) focuses it.
25
+ The panel is the region `scene`. **Alt+R** (*Go to scene*, command `focus.scene`) focuses it; rebind it in Settings → Keys. From 1.2.0 the extension registers the command itself (μClient with extension API 1.14); on an older μClient the client's own Alt+R does the same.
26
26
 
27
27
  ## Behaviour
28
28
  - Panel `scene`, singleton, default position right top, Views order 10 (after Terminal, before Channels).
29
29
  - **Show panel: auto** (per world, on the extension's settings page: off / auto / on). On auto the Scene joins Views and adds itself the first time a session knows its room, whatever the room came from (GMCP, MSDP, another extension); once per world on this device, so a Scene you closed stays closed.
30
30
  - A redraw in the same room (someone arrives, an item drops) keeps your scroll position; moving to another room scrolls back to the top. When the game sends no room id, every update counts as a move.
31
- - Right-click (or long-press) an exit or a room item for the menus other extensions add to them (`scene-exit` and `scene-item` targets).
31
+ - Right-click (or long-press) an exit or a room item for the menus other extensions add to them. The extension registers the context kinds `scene.exit` (title *Exit*, `data: { exit }`) and `scene.item` (title *Scene item*, `data: { item: { id, name, hostile? } }`), each with a schema the host checks. On a μClient before API 1.14 it publishes the 1.12 kinds `scene-exit` and `scene-item` instead; on 1.14 an entry written for the old kinds still receives the new targets in the old shape.
32
32
  - It sends nothing to the game except the exits you click, and reads no GMCP itself: the room adapters fill the scene.
33
33
 
34
34
  ## Contract
@@ -42,9 +42,12 @@ No `ctx.exports` API. The module exports, for tests and other renderers:
42
42
  | `renderScene(scene, go, opts?)` | The panel's children for one `SceneView` (`null` or `known: false` → NO ROOM YET). `go(dir)` is called on an exit click. `opts.css` passes the host classes (`mu.ui.css`), `opts.target(el, what)` is called for each exit button and present row (to mark context targets). The 1.0 two-argument form still works. |
43
43
  | `presentOf(scene)` | People first, then room items, each name once: `{ name, hostile? }[]`. |
44
44
  | `COPY`, `SCENE_CSS` | The drawn copy and the stylesheet (every rule under `.ext-panel[data-ext="scene"] .mu-scene`). |
45
+ | `ITEM_KIND`, `EXIT_KIND`, `KIND_SCHEMAS` | `'scene.item'`, `'scene.exit'` and their `data` schemas. |
46
+
47
+ `src/types.ts` augments the SDK's `ContextKinds` with `scene.item` (`SceneItemData`) and `scene.exit` (`SceneExitData`), so an extension that imports it gets `t.data` typed in `mu.menus.context({ target: 'scene.exit', … })`.
45
48
 
46
49
  ## SDK
47
- SDK 1.12: `mu.panels.register` with `show: 'auto'`, `mu.panels.touch`, `mu.sessions.each`, `mu.scene.watch` (and `SceneView.id`), `mu.sessions.send(dir, { sid })`, `mu.menus.target`, `h` and `mu.ui.css`, `mu.ui.style`.
50
+ `api` `^1.12`. SDK 1.12: `mu.panels.register` with `show: 'auto'`, `mu.panels.touch`, `mu.sessions.each`, `mu.scene.watch` (and `SceneView.id`), `mu.sessions.send(dir, { sid })`, `mu.menus.target`, `h` and `mu.ui.css`, `mu.ui.style`. SDK 1.14, used when the host has them: `mu.menus.kind` (with `JsonSchema` and `ContextKinds`), `mu.panels.focus` with `mu.commands.register`. The manifest declares `focus.scene` in `contributes.commands`, so Alt+R is listed and bound before the extension activates.
48
51
 
49
52
  ## Develop
50
53
  Made with `npm create @runmu.sh/extension` ([the quickstart](https://runmu.sh/docs/extensions/quickstart)).
package/dist/index.js CHANGED
@@ -1,5 +1,11 @@
1
1
  // src/index.ts
2
2
  import { defineExtension, h } from "@muclient/sdk";
3
+
4
+ // src/types.ts
5
+ var ITEM_KIND = "scene.item";
6
+ var EXIT_KIND = "scene.exit";
7
+
8
+ // src/index.ts
3
9
  var COPY = {
4
10
  title: "Scene",
5
11
  awaiting: "No room yet",
@@ -7,7 +13,18 @@ var COPY = {
7
13
  exits: "Exits",
8
14
  none: "none",
9
15
  hostile: "hostile",
10
- go: (dir) => `go ${dir}`
16
+ go: (dir) => `go ${dir}`,
17
+ focus: "Go to scene",
18
+ itemKind: "Scene item",
19
+ exitKind: "Exit"
20
+ };
21
+ var KIND_SCHEMAS = {
22
+ [ITEM_KIND]: {
23
+ type: "object",
24
+ required: ["item"],
25
+ properties: { item: { type: "object", required: ["id", "name"], properties: { id: { type: "string" }, name: { type: "string" }, hostile: { type: "boolean" } } } }
26
+ },
27
+ [EXIT_KIND]: { type: "object", required: ["exit"], properties: { exit: { type: "string" } } }
11
28
  };
12
29
  var R = '.ext-panel[data-ext="scene"] .mu-scene';
13
30
  var SCENE_CSS = `
@@ -85,6 +102,15 @@ var index_default = defineExtension({
85
102
  const mu = ctx.mu;
86
103
  mu.ui.style(SCENE_CSS);
87
104
  const css = { secHead: mu.ui.css.secHead, secClose: mu.ui.css.secClose, empty: mu.ui.css.empty, glow: mu.ui.css.glow };
105
+ const kinds = typeof mu.menus.kind === "function";
106
+ if (kinds) {
107
+ ctx.subscriptions.push(
108
+ mu.menus.kind({ id: ITEM_KIND, title: COPY.itemKind, schema: KIND_SCHEMAS[ITEM_KIND] }),
109
+ mu.menus.kind({ id: EXIT_KIND, title: COPY.exitKind, schema: KIND_SCHEMAS[EXIT_KIND] })
110
+ );
111
+ }
112
+ const exitTarget = (sid, exit) => kinds ? { kind: EXIT_KIND, sid, data: { exit } } : { kind: "scene-exit", sid, exit };
113
+ const itemTarget = (sid, item) => kinds ? { kind: ITEM_KIND, sid, data: { item } } : { kind: "scene-item", sid, item };
88
114
  const mount = (host, pc) => {
89
115
  const root = h("section", { class: "mu-scene", "aria-label": COPY.title, "data-focus-region": "scene", tabindex: "-1", "data-testid": "scene" });
90
116
  host.append(root);
@@ -103,8 +129,8 @@ var index_default = defineExtension({
103
129
  targets = [];
104
130
  };
105
131
  const target = (el, what) => {
106
- if ("exit" in what) targets.push(mu.menus.target(el, { kind: "scene-exit", sid, exit: what.exit }));
107
- else if ("item" in what) targets.push(mu.menus.target(el, { kind: "scene-item", sid, item: what.item }));
132
+ if ("exit" in what) targets.push(mu.menus.target(el, exitTarget(sid, what.exit)));
133
+ else if ("item" in what) targets.push(mu.menus.target(el, itemTarget(sid, what.item)));
108
134
  };
109
135
  let room = null;
110
136
  const off = mu.scene.watch((s) => {
@@ -122,6 +148,11 @@ var index_default = defineExtension({
122
148
  };
123
149
  };
124
150
  mu.panels.register({ id: "scene", title: COPY.title, singleton: true, defaultPosition: "right-top", order: 10, show: "auto", mount });
151
+ if (typeof mu.panels.focus === "function") {
152
+ mu.commands.register({ id: "focus.scene", title: COPY.focus, keys: ["Alt+R"], group: "Focus", when: "session", run: () => {
153
+ mu.panels.focus?.("scene");
154
+ } });
155
+ }
125
156
  ctx.subscriptions.push(mu.sessions.each((s) => {
126
157
  let done = false;
127
158
  return mu.scene.watch((v) => {
@@ -135,6 +166,9 @@ var index_default = defineExtension({
135
166
  });
136
167
  export {
137
168
  COPY,
169
+ EXIT_KIND,
170
+ ITEM_KIND,
171
+ KIND_SCHEMAS,
138
172
  SCENE_CSS,
139
173
  index_default as default,
140
174
  presentOf,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runmu.sh/ext-scene",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "The Scene panel: the room you are in (title, area, atmosphere, description, pose), who is present and its exits as buttons, from GMCP Room.*, Char.Items.*, MSDP or another extension.",
5
5
  "keywords": [
6
6
  "scene",
@@ -35,11 +35,11 @@
35
35
  "test": "node --test test/*.test.mjs"
36
36
  },
37
37
  "devDependencies": {
38
- "@muclient/sdk": "npm:@runmu.sh/sdk@^1.12.0",
39
- "@runmu.sh/dev": "^0.2.0",
38
+ "@muclient/sdk": "npm:@runmu.sh/sdk@^1.14.0",
39
+ "@runmu.sh/dev": "^0.3.0",
40
40
  "esbuild": "^0.28.2",
41
- "typescript": "^5.9.0",
42
- "happy-dom": "^20.0.0"
41
+ "happy-dom": "^20.0.0",
42
+ "typescript": "^5.9.0"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=20"
@@ -63,6 +63,15 @@
63
63
  "defaultPosition": "right-top",
64
64
  "singleton": true
65
65
  }
66
+ ],
67
+ "commands": [
68
+ {
69
+ "id": "focus.scene",
70
+ "title": "Go to scene",
71
+ "keys": [
72
+ "Alt+R"
73
+ ]
74
+ }
66
75
  ]
67
76
  },
68
77
  "capabilities": [
package/src/types.ts CHANGED
@@ -42,4 +42,30 @@ export interface SceneCopy {
42
42
  none: string;
43
43
  hostile: string;
44
44
  go(dir: string): string;
45
+ /** The `focus.scene` command's title. @since 1.2.0 */
46
+ focus: string;
47
+ /** The context kinds' titles (the menu's accessible label). @since 1.2.0 */
48
+ itemKind: string;
49
+ exitKind: string;
50
+ }
51
+
52
+ /** The context kind of a room item in ┤PRESENT├ (SDK 1.14). @since 1.2.0 */
53
+ export const ITEM_KIND = 'scene.item';
54
+ /** The context kind of an exit button (SDK 1.14). @since 1.2.0 */
55
+ export const EXIT_KIND = 'scene.exit';
56
+
57
+ /** `data` of a `scene.item` target. @since 1.2.0 */
58
+ export interface SceneItemData { item: SceneView['items'][number] }
59
+ /** `data` of a `scene.exit` target. @since 1.2.0 */
60
+ export interface SceneExitData { exit: string }
61
+
62
+ /**
63
+ * Types `t.data` in another extension's `mu.menus.context({ target: 'scene.item' | 'scene.exit', … })` once it
64
+ * imports this file. @since 1.2.0
65
+ */
66
+ declare module '@muclient/sdk' {
67
+ interface ContextKinds {
68
+ 'scene.item': SceneItemData;
69
+ 'scene.exit': SceneExitData;
70
+ }
45
71
  }