@moyarich/console-addon-navigation 0.0.0-stage → 0.1.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/README.md CHANGED
@@ -1,3 +1,33 @@
1
- # Temporary Holding Version
1
+ # @moyarich/console-addon-navigation
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Navigate errors and warnings. Jump between visible errors and warnings, or annotated messages when annotations are enabled.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @moyarich/console @moyarich/console-addon-navigation
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```tsx
14
+ import { Console } from "@moyarich/console";
15
+ import { createConsoleNavigationAddon } from "@moyarich/console-addon-navigation";
16
+
17
+ const addons = [createConsoleNavigationAddon()];
18
+
19
+ // Pass your application's structured messages to Console.
20
+ <Console messages={messages} addons={addons} />;
21
+ ```
22
+
23
+ The addon is optional and can be combined with other Console addons. The host application remains responsible for supplying and retaining messages.
24
+
25
+ ## Learn more
26
+
27
+ Explore [runnable package examples](./examples) and the [Console documentation](https://moyarich.github.io/console/) for available settings and integration patterns.
28
+
29
+ Review the [changelog](./CHANGELOG.md) for changes by version and [package metadata](./package.json) for dependency requirements.
30
+
31
+ ## License
32
+
33
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const consoleCore = require("@moyarich/console-core");
4
+ const CONSOLE_NAVIGATION_ADDON_ID = "@moyarich/console-addon-navigation";
5
+ const annotationLookupService = consoleCore.createConsoleServiceToken(
6
+ "@moyarich/console-addon-annotations.controller"
7
+ );
8
+ const consoleNavigationService = consoleCore.createConsoleServiceToken(
9
+ `${CONSOLE_NAVIGATION_ADDON_ID}.controller`
10
+ );
11
+ function messageMatches(message, kind, annotations) {
12
+ if (!message.id) return false;
13
+ if (kind === "error") return message.method === "error";
14
+ if (kind === "warning") {
15
+ return message.method === "warn" || message.method === "assert";
16
+ }
17
+ return annotations.has(message.id);
18
+ }
19
+ function createConsoleNavigationController(dependencies) {
20
+ let state = Object.freeze({ boundary: null });
21
+ const listeners = /* @__PURE__ */ new Set();
22
+ const update = (next) => {
23
+ state = Object.freeze(next);
24
+ for (const listener of listeners) listener();
25
+ };
26
+ const getVisibleMessages = () => {
27
+ const snapshot = dependencies.getData()?.getSnapshot();
28
+ return snapshot?.mode === "console" ? snapshot.visible : [];
29
+ };
30
+ const controller = {
31
+ getState: () => state,
32
+ subscribe(listener) {
33
+ listeners.add(listener);
34
+ return () => listeners.delete(listener);
35
+ },
36
+ jumpToMessage(id) {
37
+ const normalized = id.trim();
38
+ if (!normalized) return false;
39
+ const visible = getVisibleMessages();
40
+ if (!visible.some((message) => message.id === normalized)) return false;
41
+ const scrolled = dependencies.getViewport()?.scrollToMessage(normalized, {
42
+ block: "nearest"
43
+ }) ?? false;
44
+ if (scrolled) {
45
+ update({ currentTargetId: normalized, boundary: null });
46
+ }
47
+ return scrolled;
48
+ },
49
+ navigate(kind, direction) {
50
+ const annotations = new Set(
51
+ dependencies.getAnnotations()?.getAnnotatedMessageIds() ?? []
52
+ );
53
+ const candidates = getVisibleMessages().filter(
54
+ (message) => messageMatches(message, kind, annotations)
55
+ );
56
+ const ids = candidates.flatMap(
57
+ (message) => message.id ? [message.id] : []
58
+ );
59
+ if (ids.length === 0) {
60
+ update({
61
+ currentTargetId: state.currentTargetId,
62
+ kind,
63
+ boundary: direction === "next" ? "end" : "start"
64
+ });
65
+ return void 0;
66
+ }
67
+ const currentIndex = state.currentTargetId ? ids.indexOf(state.currentTargetId) : -1;
68
+ const targetIndex = direction === "next" ? currentIndex < 0 ? 0 : currentIndex + 1 : currentIndex < 0 ? ids.length - 1 : currentIndex - 1;
69
+ if (targetIndex < 0 || targetIndex >= ids.length) {
70
+ update({
71
+ currentTargetId: state.currentTargetId,
72
+ kind,
73
+ boundary: direction === "next" ? "end" : "start"
74
+ });
75
+ return void 0;
76
+ }
77
+ const targetId = ids[targetIndex];
78
+ const scrolled = dependencies.getViewport()?.scrollToMessage(targetId, {
79
+ block: "nearest"
80
+ }) ?? false;
81
+ if (!scrolled) return void 0;
82
+ update({ currentTargetId: targetId, kind, boundary: null });
83
+ return targetId;
84
+ },
85
+ nextError() {
86
+ return controller.navigate("error", "next");
87
+ },
88
+ previousError() {
89
+ return controller.navigate("error", "previous");
90
+ },
91
+ nextWarning() {
92
+ return controller.navigate("warning", "next");
93
+ },
94
+ previousWarning() {
95
+ return controller.navigate("warning", "previous");
96
+ },
97
+ nextAnnotated() {
98
+ return controller.navigate("annotated", "next");
99
+ },
100
+ previousAnnotated() {
101
+ return controller.navigate("annotated", "previous");
102
+ },
103
+ reset() {
104
+ update({ boundary: null });
105
+ }
106
+ };
107
+ return controller;
108
+ }
109
+ function createShortcuts(controller) {
110
+ return [
111
+ {
112
+ id: `${CONSOLE_NAVIGATION_ADDON_ID}:next-error`,
113
+ key: "F8",
114
+ preventDefault: true,
115
+ when: ({ mode }) => mode === "console",
116
+ onTrigger: () => {
117
+ controller.nextError();
118
+ }
119
+ },
120
+ {
121
+ id: `${CONSOLE_NAVIGATION_ADDON_ID}:previous-error`,
122
+ key: "F8",
123
+ shiftKey: true,
124
+ preventDefault: true,
125
+ when: ({ mode }) => mode === "console",
126
+ onTrigger: () => {
127
+ controller.previousError();
128
+ }
129
+ },
130
+ {
131
+ id: `${CONSOLE_NAVIGATION_ADDON_ID}:next-warning`,
132
+ key: "ArrowDown",
133
+ altKey: true,
134
+ preventDefault: true,
135
+ when: ({ mode }) => mode === "console",
136
+ onTrigger: () => {
137
+ controller.nextWarning();
138
+ }
139
+ },
140
+ {
141
+ id: `${CONSOLE_NAVIGATION_ADDON_ID}:previous-warning`,
142
+ key: "ArrowUp",
143
+ altKey: true,
144
+ preventDefault: true,
145
+ when: ({ mode }) => mode === "console",
146
+ onTrigger: () => {
147
+ controller.previousWarning();
148
+ }
149
+ }
150
+ ];
151
+ }
152
+ function createConsoleNavigationAddon(options = {}) {
153
+ let activeHost;
154
+ const controller = createConsoleNavigationController({
155
+ getData: () => activeHost?.services.get(consoleCore.consoleServices.data),
156
+ getViewport: () => activeHost?.services.get(
157
+ consoleCore.consoleServices.viewport
158
+ ),
159
+ getAnnotations: () => activeHost?.services.get(
160
+ annotationLookupService
161
+ )
162
+ });
163
+ return {
164
+ id: CONSOLE_NAVIGATION_ADDON_ID,
165
+ controller,
166
+ activate(host) {
167
+ activeHost = host;
168
+ host.services.provide(consoleNavigationService, controller);
169
+ if (options.keyboardShortcuts !== false) {
170
+ for (const shortcut of createShortcuts(controller)) {
171
+ host.extensions.register(
172
+ consoleCore.consoleExtensionPoints.keyboardShortcut,
173
+ shortcut,
174
+ { id: shortcut.id }
175
+ );
176
+ }
177
+ }
178
+ return () => {
179
+ activeHost = void 0;
180
+ };
181
+ }
182
+ };
183
+ }
184
+ exports.CONSOLE_NAVIGATION_ADDON_ID = CONSOLE_NAVIGATION_ADDON_ID;
185
+ exports.consoleNavigationService = consoleNavigationService;
186
+ exports.createConsoleNavigationAddon = createConsoleNavigationAddon;
187
+ exports.createConsoleNavigationController = createConsoleNavigationController;
@@ -0,0 +1,41 @@
1
+ import { type ConsoleAddon, type ConsoleDataService, type ConsoleViewportService } from "@moyarich/console-core";
2
+ export declare const CONSOLE_NAVIGATION_ADDON_ID = "@moyarich/console-addon-navigation";
3
+ export type ConsoleNavigationKind = "error" | "warning" | "annotated";
4
+ export type ConsoleNavigationDirection = "next" | "previous";
5
+ export type ConsoleNavigationBoundary = "start" | "end" | null;
6
+ export interface ConsoleNavigationState {
7
+ readonly currentTargetId?: string;
8
+ readonly kind?: ConsoleNavigationKind;
9
+ readonly boundary: ConsoleNavigationBoundary;
10
+ }
11
+ export interface ConsoleNavigationController {
12
+ getState(): ConsoleNavigationState;
13
+ subscribe(listener: () => void): () => void;
14
+ jumpToMessage(id: string): boolean;
15
+ navigate(kind: ConsoleNavigationKind, direction: ConsoleNavigationDirection): string | undefined;
16
+ nextError(): string | undefined;
17
+ previousError(): string | undefined;
18
+ nextWarning(): string | undefined;
19
+ previousWarning(): string | undefined;
20
+ nextAnnotated(): string | undefined;
21
+ previousAnnotated(): string | undefined;
22
+ reset(): void;
23
+ }
24
+ export interface ConsoleNavigationAddonOptions {
25
+ keyboardShortcuts?: boolean;
26
+ }
27
+ export interface ConsoleNavigationAddon extends ConsoleAddon {
28
+ readonly controller: ConsoleNavigationController;
29
+ }
30
+ interface ConsoleAnnotationsLookup {
31
+ getAnnotatedMessageIds(): readonly string[];
32
+ }
33
+ export declare const consoleNavigationService: import("@moyarich/console-core").ConsoleServiceToken<ConsoleNavigationController>;
34
+ interface ConsoleNavigationDependencies {
35
+ getData(): ConsoleDataService | undefined;
36
+ getViewport(): ConsoleViewportService | undefined;
37
+ getAnnotations(): ConsoleAnnotationsLookup | undefined;
38
+ }
39
+ export declare function createConsoleNavigationController(dependencies: ConsoleNavigationDependencies): ConsoleNavigationController;
40
+ export declare function createConsoleNavigationAddon(options?: ConsoleNavigationAddonOptions): ConsoleNavigationAddon;
41
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,187 @@
1
+ import { createConsoleServiceToken, consoleExtensionPoints, consoleServices } from "@moyarich/console-core";
2
+ const CONSOLE_NAVIGATION_ADDON_ID = "@moyarich/console-addon-navigation";
3
+ const annotationLookupService = createConsoleServiceToken(
4
+ "@moyarich/console-addon-annotations.controller"
5
+ );
6
+ const consoleNavigationService = createConsoleServiceToken(
7
+ `${CONSOLE_NAVIGATION_ADDON_ID}.controller`
8
+ );
9
+ function messageMatches(message, kind, annotations) {
10
+ if (!message.id) return false;
11
+ if (kind === "error") return message.method === "error";
12
+ if (kind === "warning") {
13
+ return message.method === "warn" || message.method === "assert";
14
+ }
15
+ return annotations.has(message.id);
16
+ }
17
+ function createConsoleNavigationController(dependencies) {
18
+ let state = Object.freeze({ boundary: null });
19
+ const listeners = /* @__PURE__ */ new Set();
20
+ const update = (next) => {
21
+ state = Object.freeze(next);
22
+ for (const listener of listeners) listener();
23
+ };
24
+ const getVisibleMessages = () => {
25
+ const snapshot = dependencies.getData()?.getSnapshot();
26
+ return snapshot?.mode === "console" ? snapshot.visible : [];
27
+ };
28
+ const controller = {
29
+ getState: () => state,
30
+ subscribe(listener) {
31
+ listeners.add(listener);
32
+ return () => listeners.delete(listener);
33
+ },
34
+ jumpToMessage(id) {
35
+ const normalized = id.trim();
36
+ if (!normalized) return false;
37
+ const visible = getVisibleMessages();
38
+ if (!visible.some((message) => message.id === normalized)) return false;
39
+ const scrolled = dependencies.getViewport()?.scrollToMessage(normalized, {
40
+ block: "nearest"
41
+ }) ?? false;
42
+ if (scrolled) {
43
+ update({ currentTargetId: normalized, boundary: null });
44
+ }
45
+ return scrolled;
46
+ },
47
+ navigate(kind, direction) {
48
+ const annotations = new Set(
49
+ dependencies.getAnnotations()?.getAnnotatedMessageIds() ?? []
50
+ );
51
+ const candidates = getVisibleMessages().filter(
52
+ (message) => messageMatches(message, kind, annotations)
53
+ );
54
+ const ids = candidates.flatMap(
55
+ (message) => message.id ? [message.id] : []
56
+ );
57
+ if (ids.length === 0) {
58
+ update({
59
+ currentTargetId: state.currentTargetId,
60
+ kind,
61
+ boundary: direction === "next" ? "end" : "start"
62
+ });
63
+ return void 0;
64
+ }
65
+ const currentIndex = state.currentTargetId ? ids.indexOf(state.currentTargetId) : -1;
66
+ const targetIndex = direction === "next" ? currentIndex < 0 ? 0 : currentIndex + 1 : currentIndex < 0 ? ids.length - 1 : currentIndex - 1;
67
+ if (targetIndex < 0 || targetIndex >= ids.length) {
68
+ update({
69
+ currentTargetId: state.currentTargetId,
70
+ kind,
71
+ boundary: direction === "next" ? "end" : "start"
72
+ });
73
+ return void 0;
74
+ }
75
+ const targetId = ids[targetIndex];
76
+ const scrolled = dependencies.getViewport()?.scrollToMessage(targetId, {
77
+ block: "nearest"
78
+ }) ?? false;
79
+ if (!scrolled) return void 0;
80
+ update({ currentTargetId: targetId, kind, boundary: null });
81
+ return targetId;
82
+ },
83
+ nextError() {
84
+ return controller.navigate("error", "next");
85
+ },
86
+ previousError() {
87
+ return controller.navigate("error", "previous");
88
+ },
89
+ nextWarning() {
90
+ return controller.navigate("warning", "next");
91
+ },
92
+ previousWarning() {
93
+ return controller.navigate("warning", "previous");
94
+ },
95
+ nextAnnotated() {
96
+ return controller.navigate("annotated", "next");
97
+ },
98
+ previousAnnotated() {
99
+ return controller.navigate("annotated", "previous");
100
+ },
101
+ reset() {
102
+ update({ boundary: null });
103
+ }
104
+ };
105
+ return controller;
106
+ }
107
+ function createShortcuts(controller) {
108
+ return [
109
+ {
110
+ id: `${CONSOLE_NAVIGATION_ADDON_ID}:next-error`,
111
+ key: "F8",
112
+ preventDefault: true,
113
+ when: ({ mode }) => mode === "console",
114
+ onTrigger: () => {
115
+ controller.nextError();
116
+ }
117
+ },
118
+ {
119
+ id: `${CONSOLE_NAVIGATION_ADDON_ID}:previous-error`,
120
+ key: "F8",
121
+ shiftKey: true,
122
+ preventDefault: true,
123
+ when: ({ mode }) => mode === "console",
124
+ onTrigger: () => {
125
+ controller.previousError();
126
+ }
127
+ },
128
+ {
129
+ id: `${CONSOLE_NAVIGATION_ADDON_ID}:next-warning`,
130
+ key: "ArrowDown",
131
+ altKey: true,
132
+ preventDefault: true,
133
+ when: ({ mode }) => mode === "console",
134
+ onTrigger: () => {
135
+ controller.nextWarning();
136
+ }
137
+ },
138
+ {
139
+ id: `${CONSOLE_NAVIGATION_ADDON_ID}:previous-warning`,
140
+ key: "ArrowUp",
141
+ altKey: true,
142
+ preventDefault: true,
143
+ when: ({ mode }) => mode === "console",
144
+ onTrigger: () => {
145
+ controller.previousWarning();
146
+ }
147
+ }
148
+ ];
149
+ }
150
+ function createConsoleNavigationAddon(options = {}) {
151
+ let activeHost;
152
+ const controller = createConsoleNavigationController({
153
+ getData: () => activeHost?.services.get(consoleServices.data),
154
+ getViewport: () => activeHost?.services.get(
155
+ consoleServices.viewport
156
+ ),
157
+ getAnnotations: () => activeHost?.services.get(
158
+ annotationLookupService
159
+ )
160
+ });
161
+ return {
162
+ id: CONSOLE_NAVIGATION_ADDON_ID,
163
+ controller,
164
+ activate(host) {
165
+ activeHost = host;
166
+ host.services.provide(consoleNavigationService, controller);
167
+ if (options.keyboardShortcuts !== false) {
168
+ for (const shortcut of createShortcuts(controller)) {
169
+ host.extensions.register(
170
+ consoleExtensionPoints.keyboardShortcut,
171
+ shortcut,
172
+ { id: shortcut.id }
173
+ );
174
+ }
175
+ }
176
+ return () => {
177
+ activeHost = void 0;
178
+ };
179
+ }
180
+ };
181
+ }
182
+ export {
183
+ CONSOLE_NAVIGATION_ADDON_ID,
184
+ consoleNavigationService,
185
+ createConsoleNavigationAddon,
186
+ createConsoleNavigationController
187
+ };
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "@moyarich/console-addon-navigation",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "First-party stable-ID message navigation for @moyarich/console hosts.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "development": "./src/index.ts",
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "publishConfig": {
22
+ "registry": "https://npm.pkg.github.com"
23
+ },
24
+ "scripts": {
25
+ "build": "vite build && tsc -p tsconfig.build.json",
26
+ "typecheck": "tsc --noEmit"
27
+ },
28
+ "peerDependencies": {
29
+ "@moyarich/console-core": "^0.1.0"
30
+ },
31
+ "devDependencies": {
32
+ "@moyarich/console-core": "0.1.0",
33
+ "typescript": "^5.9.2",
34
+ "vite": "^7.1.5"
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/moyarich/console.git",
39
+ "directory": "packages/addons/navigation"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/moyarich/console/issues"
43
+ },
44
+ "homepage": "https://github.com/moyarich/console#readme",
45
+ "keywords": [
46
+ "console",
47
+ "addon",
48
+ "navigation",
49
+ "errors",
50
+ "warnings",
51
+ "developer-tools"
52
+ ],
53
+ "publisher": "moyarich",
54
+ "author": "Moya Richards",
55
+ "private": false
56
+ }