@runmu.sh/ext-scene 1.0.3

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 ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ ## 1.0.3
4
+
5
+ - No longer bundled with μClient: `builtin` and `defaultEnabled` are gone from the manifest. Install Scene from Extensions → Discover like any other extension. The panel is unchanged.
6
+
7
+ ## 1.0.2
8
+
9
+ - Its own repository, [runmu-sh/ext-scene](https://github.com/runmu-sh/ext-scene), made with `npm create @runmu.sh/extension` and built against `@runmu.sh/sdk` from npm. The package is `@runmu.sh/ext-scene`. The panel is unchanged.
10
+
11
+ ## 1.0.1
12
+
13
+ - The empty state reads NO ROOM YET (was "Awaiting signal…"): an upright, uppercase, tracked faint label, as the 2026-09-29 reference.
14
+
15
+ ## 1.0.0
16
+
17
+ - The Scene panel as an extension. It was part of the μClient core; it now draws the room through `mu.scene.watch` (SDK 1.4). Nothing changes for players: it is built in, on by default and in the default layout.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 runmu.sh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # Scene (`@runmu.sh/ext-scene`, id `scene`)
2
+
3
+ The Scene panel of [μClient](https://runmu.sh) (R-SCENE): the room you are in, its exits and who is there.
4
+
5
+ First-party, on the [marketplace](https://runmu.sh/marketplace/x/scene). Install it from **☰ → Extensions → Discover**; once installed it is on in every world and takes the Scene slot of the default layout (right top). Turn it off per world in **Extensions → Installed**.
6
+
7
+ Until 2026-09-29 the panel was part of the client core, and until 1.0.3 it was bundled with the client. The core still tracks the room for every session; this extension only draws it.
8
+
9
+ ## Where the room comes from
10
+ The host keeps one scene per session and fills it from:
11
+
12
+ | Source | Fields |
13
+ |---|---|
14
+ | GMCP `Room.Info` | `name`, `num`/`id`/`vnum`, `area`/`zone`, `desc`/`description`, `atmosphere`/`weather`, `pose`, `exits` (object, list or string) |
15
+ | GMCP `Room.Players`, `Room.AddPlayer`, `Room.RemovePlayer` | Present (people) |
16
+ | GMCP `Char.Items.List` / `Add` / `Remove` with `location: "room"` | Present (items; `hostile: true` is marked) |
17
+ | MSDP `ROOM_NAME`, `ROOM_VNUM`, `ROOM_EXITS`, `AREA_NAME` (or `ROOM.*`) | Title, exits, area |
18
+ | Another extension: `mu.scene.set({ title, area, desc, atmosphere, pose, exits, present, items })` | Any field |
19
+
20
+ Package names are case-insensitive. Entering a new room clears Present. The room title is also the session's location (the world switcher shows it).
21
+
22
+ ## What it looks like
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 `┤EXITS├` and `┤PRESENT├` section heads (gold, the host's `.sec-head`) over `▸` lists. Each exit is a button: clicking it sends the direction as if typed. A hostile item carries a small `hostile` tag in `--alert`. An empty list says `none`, and before any room arrives the panel says NO ROOM YET (an upright, uppercase, tracked faint label).
24
+
25
+ The panel is the region `scene`, so **Alt+R** (*Go to scene*) focuses it.
26
+
27
+ ## Behaviour
28
+ - Panel `scene`, singleton, default position right top, Views order 10 (after Terminal, before Channels).
29
+ - The first `Room.*` package of a session adds the panel if it is not open (R-AUTO-PANELS), once per world on this device, so a Scene you closed stays closed.
30
+ - It sends nothing to the game except the exits you click. It adds no `Core.Supports` of its own (the backend's base set already has `Room 1`).
31
+
32
+ ## SDK
33
+ Uses `mu.panels.register` (with `order`, SDK 1.4), `mu.scene.watch` (SDK 1.4), `mu.sessions.send`, `mu.gmcp.on`, `mu.panels.autoAdd` and `mu.ui.style`. It exports no API. `renderScene` and `presentOf` are exported for tests.
34
+
35
+ ## Develop
36
+ Made with `npm create @runmu.sh/extension` ([the quickstart](https://runmu.sh/docs/extensions/quickstart)).
37
+
38
+ ```sh
39
+ npm install
40
+ npm run build # src/index.ts → dist/index.js, then the manifest check
41
+ npm run typecheck # against @muclient/sdk (npm:@runmu.sh/sdk)
42
+ npm run dev # dev server on http://localhost:5199/ with hot reload
43
+ ```
44
+
45
+ In μClient: **☰ → Extensions → Advanced → Developer → load from dev server**.
46
+
47
+ ## Publish
48
+ The repository is linked to the marketplace listing [scene](https://runmu.sh/marketplace/x/scene) (publisher `bitmuse`): every version tag is imported within seconds (webhook), or within 30 minutes (poll).
49
+
50
+ ```sh
51
+ # bump version in package.json and add a CHANGELOG entry, then:
52
+ npm run build && git commit -am "Scene x.y.z" && git tag vx.y.z && git push --follow-tags
53
+ ```
54
+
55
+ `dist/` is committed: the marketplace builds nothing. Versions are immutable.
56
+
57
+ ## License
58
+ MIT.
package/dist/index.js ADDED
@@ -0,0 +1,112 @@
1
+ // src/index.ts
2
+ import { defineExtension } from "@muclient/sdk";
3
+ var COPY = {
4
+ title: "Scene",
5
+ awaiting: "No room yet",
6
+ present: "Present",
7
+ exits: "Exits",
8
+ none: "none",
9
+ hostile: "hostile",
10
+ go: (dir) => `go ${dir}`
11
+ };
12
+ var SCENE_CSS = `
13
+ .mu-scene { height: 100%; overflow-y: auto; background: var(--bg-elev); padding: .7rem .85rem 1rem; font-size: .9rem; color: var(--fg); box-sizing: border-box; }
14
+ .mu-scene:focus { outline: none; }
15
+ .mu-scene .title { margin: 0 0 .5rem; padding-bottom: .4rem; font-weight: 400; font-size: .9rem; letter-spacing: .16em; text-transform: uppercase; color: var(--accent-bright); border-bottom: 1px solid var(--border-bright); }
16
+ .mu-scene .area { font-size: .66rem; letter-spacing: .2em; text-transform: uppercase; color: var(--fg-faint); margin: -.2rem 0 .4rem; }
17
+ .mu-scene .atmo { color: var(--fg-dim); font-style: italic; margin: 0 0 .4rem; }
18
+ .mu-scene .desc { margin: 0 0 .4rem; white-space: pre-wrap; }
19
+ .mu-scene .pose { color: var(--fg-dim); font-style: italic; margin: 0 0 .5rem; padding-left: .5rem; border-left: 2px solid var(--border-bright); }
20
+ .mu-scene .list { list-style: none; margin: 0; padding: 0; }
21
+ .mu-scene .list li { padding: .08rem 0; color: var(--fg); }
22
+ .mu-scene .sigil { color: var(--accent); margin-right: .6ch; }
23
+ .mu-scene .exit { color: inherit; text-align: left; transition: color .12s ease; }
24
+ .mu-scene .exit:hover { color: var(--accent-bright); }
25
+ .mu-scene .exit:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: 1px; }
26
+ .mu-scene .hostile { color: var(--alert); font-size: .72rem; margin-left: 6px; }
27
+ .mu-scene .none { margin: .1rem 0; font-style: normal; }
28
+ .mu-scene .awaiting { margin: 0; color: var(--fg-faint); font-style: normal; font-size: .64rem; letter-spacing: .14em; text-transform: uppercase; }
29
+ `;
30
+ function el(tag, props = {}, ...kids) {
31
+ const e = document.createElement(tag);
32
+ for (const [k, v] of Object.entries(props)) {
33
+ if (v === void 0 || v === false) continue;
34
+ if (k === "class") e.className = String(v);
35
+ else e.setAttribute(k, v === true ? "" : String(v));
36
+ }
37
+ for (const k of kids) if (k !== null && k !== false) e.append(k);
38
+ return e;
39
+ }
40
+ function presentOf(s) {
41
+ const seen = /* @__PURE__ */ new Set();
42
+ const out = [];
43
+ for (const n of s.present) if (!seen.has(n)) {
44
+ seen.add(n);
45
+ out.push({ name: n });
46
+ }
47
+ for (const it of s.items) if (!seen.has(it.name)) {
48
+ seen.add(it.name);
49
+ out.push(it.hostile ? { name: it.name, hostile: true } : { name: it.name });
50
+ }
51
+ return out;
52
+ }
53
+ var sectionHead = (label) => el("div", { class: "sec-head", role: "heading", "aria-level": "3" }, label, el("span", { class: "sec-close", "aria-hidden": "true" }));
54
+ var sigil = () => el("span", { class: "sigil", "aria-hidden": "true" }, "\u25B8");
55
+ function renderScene(s, go) {
56
+ if (!s || !s.known) return [el("p", { class: "empty awaiting", "data-testid": "scene-empty" }, COPY.awaiting)];
57
+ const out = [el("h3", { class: "title glow-text", role: "heading", "aria-level": "2", "data-testid": "scene-title" }, s.title)];
58
+ if (s.area) out.push(el("div", { class: "area", "data-testid": "scene-area" }, s.area));
59
+ if (s.atmosphere) out.push(el("p", { class: "atmo" }, s.atmosphere));
60
+ if (s.desc) out.push(el("p", { class: "desc" }, s.desc));
61
+ if (s.pose) out.push(el("p", { class: "pose" }, s.pose));
62
+ out.push(sectionHead(COPY.exits));
63
+ if (s.exits.length) {
64
+ const ul = el("ul", { class: "list", "data-testid": "scene-exits" });
65
+ for (const dir of s.exits) {
66
+ const b = el("button", { class: "exit", type: "button", title: COPY.go(dir) }, sigil(), dir);
67
+ b.addEventListener("click", () => go(dir));
68
+ ul.append(el("li", {}, b));
69
+ }
70
+ out.push(ul);
71
+ } else out.push(el("p", { class: "empty none" }, COPY.none));
72
+ out.push(sectionHead(COPY.present));
73
+ const present = presentOf(s);
74
+ if (present.length) {
75
+ const ul = el("ul", { class: "list", "data-testid": "scene-present" });
76
+ for (const p of present) ul.append(el("li", {}, sigil(), p.name, p.hostile ? el("span", { class: "hostile" }, COPY.hostile) : null));
77
+ out.push(ul);
78
+ } else out.push(el("p", { class: "empty none" }, COPY.none));
79
+ return out;
80
+ }
81
+ var index_default = defineExtension({
82
+ activate(ctx) {
83
+ const mu = ctx.mu;
84
+ mu.ui.style(SCENE_CSS);
85
+ const mount = (host, pc) => {
86
+ const root = el("section", { class: "mu-scene", "aria-label": COPY.title, "data-focus-region": "scene", tabindex: "-1", "data-testid": "scene" });
87
+ host.append(root);
88
+ const sid = pc.sid;
89
+ const go = (dir) => {
90
+ if (sid) void mu.sessions.send(dir, sid);
91
+ };
92
+ if (!sid) {
93
+ root.replaceChildren(...renderScene(null, go));
94
+ return () => root.remove();
95
+ }
96
+ const off = mu.scene.watch((s) => root.replaceChildren(...renderScene(s, go)), sid);
97
+ return () => {
98
+ off();
99
+ root.remove();
100
+ };
101
+ };
102
+ mu.panels.register({ id: "scene", title: COPY.title, singleton: true, defaultPosition: "right-top", order: 10, mount });
103
+ mu.gmcp.on("Room", (_d, { sid }) => mu.panels.autoAdd("scene", sid));
104
+ }
105
+ });
106
+ export {
107
+ COPY,
108
+ SCENE_CSS,
109
+ index_default as default,
110
+ presentOf,
111
+ renderScene
112
+ };
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@runmu.sh/ext-scene",
3
+ "version": "1.0.3",
4
+ "description": "The Scene panel: the room you are in (title, area, atmosphere, description, pose), its exits as buttons and who is present, from GMCP Room.*, Char.Items.*, MSDP or another extension.",
5
+ "keywords": [
6
+ "scene",
7
+ "room",
8
+ "exits",
9
+ "gmcp",
10
+ "msdp",
11
+ "panel"
12
+ ],
13
+ "license": "MIT",
14
+ "homepage": "https://runmu.sh/marketplace/x/scene",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/runmu-sh/ext-scene.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/runmu-sh/ext-scene/issues"
21
+ },
22
+ "type": "module",
23
+ "exports": "./dist/index.js",
24
+ "files": [
25
+ "dist",
26
+ "README.md",
27
+ "CHANGELOG.md"
28
+ ],
29
+ "scripts": {
30
+ "build": "node scripts/build.mjs",
31
+ "dev": "node scripts/dev.mjs",
32
+ "typecheck": "tsc --noEmit",
33
+ "check": "node scripts/build.mjs --check"
34
+ },
35
+ "devDependencies": {
36
+ "@muclient/sdk": "npm:@runmu.sh/sdk@^1.6.0",
37
+ "@runmu.sh/dev": "^0.1.0",
38
+ "esbuild": "^0.28.2",
39
+ "typescript": "^5.9.0"
40
+ },
41
+ "engines": {
42
+ "node": ">=20"
43
+ },
44
+ "muclient": {
45
+ "id": "scene",
46
+ "api": "^1.4",
47
+ "source": "src/index.ts",
48
+ "displayName": "Scene",
49
+ "description": "The Scene panel: the current room, its exits as buttons and who is present, from GMCP Room.*, Char.Items.*, MSDP or another extension.",
50
+ "categories": [
51
+ "interface",
52
+ "mapping"
53
+ ],
54
+ "contributes": {
55
+ "panels": [
56
+ {
57
+ "id": "scene",
58
+ "title": "Scene"
59
+ }
60
+ ],
61
+ "gmcp": [
62
+ "Room 1"
63
+ ]
64
+ },
65
+ "capabilities": [
66
+ "read-output",
67
+ "send-commands"
68
+ ],
69
+ "publisher": "bitmuse"
70
+ }
71
+ }