@aiot-nexus/plugin-sdk 3.1.1 → 3.3.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.
@@ -1,8 +1,28 @@
1
1
  :root {
2
+ --bg: var(--nexus-color-background, #eef1f4);
3
+ --panel: var(--nexus-color-surface, #ffffff);
4
+ --raise: var(--nexus-color-surface-raised, #f7f9fa);
5
+ --hover: var(--nexus-color-surface-hover, #eef3f6);
6
+ --line: var(--nexus-color-border, #d7dde3);
7
+ --line-strong: var(--nexus-color-border-strong, #c2cbd3);
8
+ --fg: var(--nexus-color-text, #212a32);
9
+ --fg-dim: var(--nexus-color-text-muted, #5a6772);
10
+ --brand: var(--nexus-color-primary, #8fc31f);
11
+ --brand-text: var(--nexus-color-accent, #6f9a15);
12
+ --on-brand: var(--nexus-color-on-primary, #ffffff);
13
+ --primary-action: var(--nexus-color-primary, #8fc31f);
14
+ --on-primary: var(--nexus-color-on-primary, #ffffff);
15
+ --on-negative: var(--nexus-color-on-negative, #ffffff);
16
+ --focus-ring: var(--nexus-color-accent, #6f9a15);
17
+ --brand-soft: var(--nexus-color-primary-soft, rgba(143, 195, 31, 0.12));
18
+ --positive: var(--nexus-color-positive, #1f9a4e);
2
19
  color-scheme: light;
3
- font-family: "Segoe UI", Arial, sans-serif;
4
- background: #f3f5f7;
5
- color: #20252b;
20
+ font-family: "Noto Sans SC", "Source Han Sans SC", "Microsoft YaHei",
21
+ "Segoe UI", system-ui, sans-serif;
22
+ background: var(--bg);
23
+ color: var(--fg);
24
+ font-size: 14px;
25
+ letter-spacing: 0;
6
26
  }
7
27
 
8
28
  * {
@@ -16,7 +36,8 @@
16
36
  body {
17
37
  margin: 0;
18
38
  min-height: 0;
19
- background: #f3f5f7;
39
+ background: var(--bg);
40
+ color: var(--fg);
20
41
  }
21
42
 
22
43
  .plugin-shell {
@@ -27,9 +48,9 @@ body {
27
48
 
28
49
  .plugin-header,
29
50
  .echo-panel {
30
- border: 1px solid #d4d9df;
31
- border-radius: 8px;
32
- background: #ffffff;
51
+ border: 1px solid var(--line);
52
+ border-radius: 10px;
53
+ background: var(--panel);
33
54
  }
34
55
 
35
56
  .plugin-header {
@@ -47,22 +68,24 @@ p {
47
68
  }
48
69
 
49
70
  h1 {
50
- font-size: 22px;
71
+ font-size: 18px;
72
+ font-weight: 600;
51
73
  }
52
74
 
53
75
  h2 {
54
- font-size: 15px;
76
+ font-size: 14.5px;
77
+ font-weight: 600;
55
78
  }
56
79
 
57
80
  p,
58
81
  .result-row > span {
59
82
  margin-top: 4px;
60
- color: #65717e;
83
+ color: var(--fg-dim);
61
84
  font-size: 12px;
62
85
  }
63
86
 
64
87
  .status {
65
- color: #12663f;
88
+ color: var(--positive);
66
89
  font-size: 12px;
67
90
  font-weight: 600;
68
91
  }
@@ -87,24 +110,42 @@ label {
87
110
  input {
88
111
  min-width: 0;
89
112
  height: 36px;
90
- border: 1px solid #aeb7c2;
91
- border-radius: 6px;
113
+ border: 1px solid var(--line-strong);
114
+ border-radius: 7px;
92
115
  padding: 0 10px;
93
- background: #ffffff;
94
- color: #20252b;
116
+ background: var(--panel);
117
+ color: var(--fg);
95
118
  font: inherit;
96
119
  }
97
120
 
98
121
  button {
99
122
  height: 36px;
100
- border: 1px solid #1768a6;
101
- border-radius: 6px;
123
+ border: 1px solid var(--brand);
124
+ border-radius: 7px;
102
125
  padding: 0 16px;
103
- background: #1768a6;
104
- color: #ffffff;
126
+ background: var(--brand);
127
+ color: var(--on-brand);
105
128
  font: inherit;
106
129
  font-weight: 600;
107
130
  cursor: pointer;
131
+ transition: filter 0.15s ease, background-color 0.15s ease,
132
+ border-color 0.15s ease, color 0.15s ease;
133
+ }
134
+
135
+ button:not(:disabled):hover {
136
+ border-color: var(--primary-action);
137
+ background: var(--primary-action);
138
+ color: var(--on-primary);
139
+ }
140
+
141
+ button:focus-visible,
142
+ input:focus-visible {
143
+ outline: 2px solid var(--focus-ring);
144
+ outline-offset: 2px;
145
+ }
146
+
147
+ input:hover {
148
+ border-color: var(--brand);
108
149
  }
109
150
 
110
151
  button:disabled {
@@ -117,7 +158,7 @@ button:disabled {
117
158
  grid-template-columns: 80px minmax(0, 1fr);
118
159
  gap: 10px;
119
160
  margin-top: 14px;
120
- border-top: 1px solid #e7eaee;
161
+ border-top: 1px solid var(--line);
121
162
  padding-top: 12px;
122
163
  }
123
164
 
@@ -135,37 +176,22 @@ output {
135
176
  }
136
177
 
137
178
  :root[data-theme="dark"] {
179
+ --bg: var(--nexus-color-background, #101418);
180
+ --panel: var(--nexus-color-surface, #1b222a);
181
+ --raise: var(--nexus-color-surface-raised, #171c22);
182
+ --hover: var(--nexus-color-surface-hover, #222a34);
183
+ --line: var(--nexus-color-border, #2a333e);
184
+ --line-strong: var(--nexus-color-border-strong, #38434f);
185
+ --fg: var(--nexus-color-text, #e2e8ee);
186
+ --fg-dim: var(--nexus-color-text-muted, #97a3af);
187
+ --brand-text: var(--nexus-color-accent, #a5d43e);
188
+ --primary-action: var(--nexus-color-primary, #8fc31f);
189
+ --on-primary: var(--nexus-color-on-primary, #ffffff);
190
+ --on-negative: var(--nexus-color-on-negative, #101418);
191
+ --focus-ring: var(--nexus-color-accent, #a5d43e);
192
+ --brand-soft: var(--nexus-color-primary-soft, rgba(143, 195, 31, 0.14));
193
+ --positive: var(--nexus-color-positive, #46b96e);
138
194
  color-scheme: dark;
139
- background: #16191d;
140
- color: #edf0f2;
141
- }
142
-
143
- :root[data-theme="dark"] body {
144
- background: #16191d;
145
- color: #edf0f2;
146
- }
147
-
148
- :root[data-theme="dark"] .plugin-header,
149
- :root[data-theme="dark"] .echo-panel {
150
- border-color: #454b52;
151
- background: #24282d;
152
- }
153
-
154
- :root[data-theme="dark"] p,
155
- :root[data-theme="dark"] .result-row > span {
156
- color: #aeb7c2;
157
- }
158
-
159
- :root[data-theme="dark"] input {
160
- border-color: #59616a;
161
- background: #191c20;
162
- color: #edf0f2;
163
- }
164
-
165
- :root[data-theme="dark"] .result-row {
166
- border-top-color: #454b52;
167
- }
168
-
169
- :root[data-theme="dark"] .status {
170
- color: #75d6a5;
195
+ background: var(--bg);
196
+ color: var(--fg);
171
197
  }
@@ -1,6 +1,6 @@
1
1
  # __DISPLAY_NAME_MARKDOWN__
2
2
 
3
- AIoT Nexus independent PluginFrame plugin generated from the `sqlite` template.
3
+ AIoT Toolkits independent PluginFrame plugin generated from the `sqlite` template.
4
4
 
5
5
  ```bash
6
6
  npm install
@@ -8,7 +8,7 @@ npm run typecheck
8
8
  npm run build
9
9
  npm run dev:web
10
10
  npm run dev
11
- npm run dev:nexus -- --host C:\path\to\nexus.exe
11
+ npm run dev:nexus -- --host "C:\path\to\AIoT Toolkits.exe"
12
12
  ```
13
13
 
14
14
  SDK checks for a newer `@aiot-nexus/plugin-sdk` in two steps. The package `postinstall` refreshes a user cache; npm 7+ hides dependency lifecycle output by default, so use `npm install --foreground-scripts` to see it. The `nexus-plugin` CLI reads that cache and prints npm-style notices when it is fresh; missing or older-than-24-hours cache data is checked in the background without blocking the command. `npm install --ignore-scripts` skips only the install check, while CLI checks remain available.
@@ -17,7 +17,7 @@ The check follows the npm-configured registry, falls back to the official npm re
17
17
 
18
18
  Every `npm run build` rebuilds `better-sqlite3@8.7.0` for Electron 22.3.27 with `@electron/rebuild@3.7.2` before packaging. `npm run rebuild:native` remains available as a standalone diagnostic command.
19
19
 
20
- The deployable plugin package is `dist/__PLUGIN_ID__/`. For a manual installation, stop Nexus before replacing `Plugins/__PLUGIN_ID__/`, then start it again. During development, use `npm run dev:nexus` so the CLI coordinates Nexus shutdown, atomic replacement, and restart for trusted `mainEntry` changes. Runtime data is stored in the host-provided plugin data directory and is not written into the plugin package.
20
+ The deployable plugin package is `dist/__PLUGIN_ID__/`. For a manual installation, stop AIoT Toolkits before replacing `Plugins/__PLUGIN_ID__/`, then start it again. During development, use `npm run dev:nexus` so the CLI coordinates AIoT Toolkits shutdown, atomic replacement, and restart for trusted `mainEntry` changes. Runtime data is stored in the host-provided plugin data directory and is not written into the plugin package.
21
21
 
22
22
  The template is a schema-aware SQLite database browser. It opens `plugin.db` in the plugin data directory by default and initializes the `plugin_records` sample table there. **Open Database** calls Electron's directly imported `dialog.showOpenDialog()` from trusted `mainEntry`; it accepts a single existing SQLite file, including files without an extension. The renderer only displays the selected path and invokes parameterized operations. It never receives an Electron object and does not expose a path input, arbitrary SQL, DDL, `ATTACH`, or a SQL console.
23
23
 
@@ -43,19 +43,23 @@ if (!result.canceled && result.filePaths[0]) {
43
43
  }
44
44
  ```
45
45
 
46
- For related workflows, use `properties: ["openDirectory", "createDirectory"]` to choose a folder or `dialog.showSaveDialog()` to choose a save path. These calls belong in trusted `mainEntry`; the renderer should invoke a registered business operation such as `sqlite.chooseDatabase` through `client.invokeMain()` and receive only the result it needs. Native dialogs are unavailable in `npm run dev:web`; use `npm run dev` or a formal Nexus host.
46
+ For related workflows, use `properties: ["openDirectory", "createDirectory"]` to choose a folder or `dialog.showSaveDialog()` to choose a save path. These calls belong in trusted `mainEntry`; the renderer should invoke a registered business operation such as `sqlite.chooseDatabase` through `client.invokeMain()` and receive only the result it needs. Native dialogs are unavailable in `npm run dev:web`; use `npm run dev` or a formal AIoT Toolkits host.
47
47
 
48
48
  External files are opened read/write only after a separate candidate connection can query `sqlite_schema`. Canceling the dialog or selecting a corrupt, locked, or inaccessible file keeps the current connection. The latest external path is atomically stored as `sqlite-browser-state.json` in the plugin data directory. Startup restores it when valid; otherwise the template clears the saved selection, falls back to `plugin.db`, and returns a visible notice. External databases do not receive the sample schema or journal pragma.
49
49
 
50
50
  The table selector lists ordinary user tables from the `main` schema. Dynamic headers show declared type, SQLite affinity, primary-key order, NULL, default, and generated-column metadata. The editor supports default/NULL/value modes for inserts and unchanged/NULL/value modes for updates. INTEGER input uses exact decimal integers, REAL must be finite, NUMERIC accepts integer, real, or text values, and BLOB input uses even-length hexadecimal text. The 1 MiB field and 4 MiB row limits count decoded BLOB bytes rather than hexadecimal characters. Ordinary rowid tables prefer an unshadowed built-in rowid; `WITHOUT ROWID` tables, or tables that shadow every rowid alias, fall back only to primary-key columns that are all non-NULL. A table without a unique locator remains browsable and insertable, but update and delete are unavailable. Primary-key locators larger than 16 KiB are also treated as browse-only so a page cannot copy unbounded keys into the trusted main process and iframe. Update and delete run in a transaction and must affect exactly one row. TEXT previews are capped at 16 KiB of UTF-8 data; BLOB previews show the byte length and first 32 bytes.
51
51
 
52
- The renderer follows `context.language` and `context.themeMode` from Nexus and preserves unfinished editor values during in-place context updates. The plugin page has no language or theme selector; the controls shown by `npm run dev` belong to the debug host. Below that toolbar, the iframe has no host padding, border, minimum size, or content-driven height and fills the remaining host viewport. This SDK template deliberately has no Quasar dependency, so its data grid uses a native `<table>` as a dependency-minimized template exception. It still provides pagination, contained horizontal scrolling, light/dark themes, `en-US` / `zh-CN`, and busy/empty/error states. Edit renderer messages in `src/renderer/app.ts`; edit host-shell titles and descriptions in `src/manifest.json` resources.
52
+ The renderer follows `context.language` and `context.themeMode` from AIoT Toolkits and preserves unfinished editor values during in-place context updates. The plugin page has no language or theme selector; the controls shown by `npm run dev` belong to the debug host. Below that toolbar, the iframe has no host padding, border, minimum size, or content-driven height and fills the remaining host viewport. This SDK template deliberately has no Quasar dependency, so its data grid uses a native `<table>` as a dependency-minimized template exception. It still provides pagination, contained horizontal scrolling, light/dark themes, `en-US` / `zh-CN`, and busy/empty/error states. Edit renderer messages in `src/renderer/app.ts`; edit host-shell titles and descriptions in `src/manifest.json` resources.
53
53
 
54
- The current SDK has no generic business-event subscription API. For a long-running operation, register a fast `job.start` operation that returns a `taskId`, then expose `job.getStatus` and optional `job.cancel` for renderer polling. See the Nexus repository's plugin IPC and event notification example for the full contract and code.
54
+ SDK 3.2 automatically maps the active `context.theme` palette to `--nexus-color-*` variables and sets `data-nexus-theme="light|dark"` on the document root. This template consumes those variables with local light/dark fallbacks, so it still renders on an older host. Merely upgrading the SDK does not recolor custom CSS: use the namespaced variables or read the optional palette explicitly, for example `const primary = context.theme?.light.primary`.
55
55
 
56
- Use `npm run dev:web` for renderer-only layout, styling, localization, and responsive checks in the system browser. It runs `build:web`, writes an isolated package under `.nexus-plugin-dev/web-package/`, and does not rebuild or load `better-sqlite3`, execute `mainEntry`, provide Node or Electron main-process APIs, copy native runtime files, or overwrite the deployable `dist` package. It builds with source maps, serves only on `127.0.0.1:4173`, and opens the browser automatically. The page explicitly reports `PluginCapabilityUnsupported`; file selection, table loading, and CRUD require `npm run dev` or a formal Nexus host. Pass `-- --no-open` or `-- --port <port>` when needed.
56
+ For a plugin-owned theme, create the client with `createPluginFrameClient({ autoApplyHostTheme: false })`. The palette remains available from `await client.waitForContext()`, but the SDK will not write CSS variables or `data-nexus-theme`. It never overwrites plugin-owned `--bg`, `--accent`, `data-theme`, or `color-scheme` values.
57
57
 
58
- Use `npm run dev:nexus -- --host C:\path\to\nexus.exe` to run the plugin inside a compiled, packaged Nexus. The initial deployment and every full package replacement are atomic. Renderer-only and safe manifest updates keep the Nexus PID; `mainEntry`, dependency, native-module, or restart-sensitive manifest changes run the complete build (including the Electron 22.3.27 native rebuild), stop Nexus, replace the package, and start a new Nexus process. The database remains under the host-provided plugin data directory across host restarts. Failed builds keep the previous deployment, and launch or readiness failures roll the package back and restore the previous host. Add `--no-devtools`, `--inspect[=<port>]`, `--inspect-brk[=<port>]`, `--surface`, or `--contribution-id` after the second `--` as needed.
58
+ The current SDK has no generic business-event subscription API. For a long-running operation, register a fast `job.start` operation that returns a `taskId`, then expose `job.getStatus` and optional `job.cancel` for renderer polling. See the AIoT Toolkits repository's plugin IPC and event notification example for the full contract and code.
59
+
60
+ Use `npm run dev:web` for renderer-only layout, styling, localization, and responsive checks in the system browser. It runs `build:web`, writes an isolated package under `.nexus-plugin-dev/web-package/`, and does not rebuild or load `better-sqlite3`, execute `mainEntry`, provide Node or Electron main-process APIs, copy native runtime files, or overwrite the deployable `dist` package. It builds with source maps, serves only on `127.0.0.1:4173`, and opens the browser automatically. The page explicitly reports `PluginCapabilityUnsupported`; file selection, table loading, and CRUD require `npm run dev` or a formal AIoT Toolkits host. Pass `-- --no-open` or `-- --port <port>` when needed.
61
+
62
+ Use `npm run dev:nexus -- --host "C:\path\to\AIoT Toolkits.exe"` to run the plugin inside a compiled, packaged AIoT Toolkits. The initial deployment and every full package replacement are atomic. Renderer-only and safe manifest updates keep the AIoT Toolkits PID; `mainEntry`, dependency, native-module, or restart-sensitive manifest changes run the complete build (including the Electron 22.3.27 native rebuild), stop AIoT Toolkits, replace the package, and start a new AIoT Toolkits process. The database remains under the host-provided plugin data directory across host restarts. Failed builds keep the previous deployment, and launch or readiness failures roll the package back and restore the previous host. Add `--no-devtools`, `--inspect[=<port>]`, `--inspect-brk[=<port>]`, `--surface`, or `--contribution-id` after the second `--` as needed.
59
63
 
60
64
  When this project was created from a local SDK archive, `vendor/aiot-nexus-plugin-sdk.tgz` is part of the source project and must be kept when the project is moved or committed. An explicit local upgrade copies the target archive to `vendor/aiot-nexus-plugin-sdk-<version>.tgz` and updates the exact file dependency.
61
65
 
@@ -63,4 +67,4 @@ For a local SDK archive, the tgz version remains the plugin's current SDK versio
63
67
 
64
68
  For trusted-main breakpoints, open this plugin directory in VS Code, set breakpoints in `src/main.ts`, and run `Debug Nexus plugin`. To attach manually, start `npm run dev:debug` and select `Attach Nexus plugin main process (9229)`. Renderer DevTools opens automatically, with `src/renderer/app.ts` available through the development source map. The default debug build also runs the Electron 22.3.27 native rebuild before launch. Use `npm run dev -- --no-build` only when intentionally reusing a compatible current `dist` output.
65
69
 
66
- The independent host stores the SQLite database and settings under `.nexus-plugin-dev/data/` and mirrors the PluginFrame protocol and current Host API surface. Its simulated Nexus application version defaults to the version recorded by the SDK build; pass `npm run dev -- --host-version <X.Y.Z>` to exercise another compatibility branch. Run the deployable `dist/__PLUGIN_ID__/` in Nexus before delivery because the standalone host does not reproduce the full application shell or routing state.
70
+ The independent host stores the SQLite database and settings under `.nexus-plugin-dev/data/` and mirrors the PluginFrame protocol and current Host API surface. Its simulated AIoT Toolkits application version defaults to the version recorded by the SDK build; pass `npm run dev -- --host-version <X.Y.Z>` to exercise another compatibility branch. Run the deployable `dist/__PLUGIN_ID__/` in AIoT Toolkits before delivery because the standalone host does not reproduce the full application shell or routing state.
@@ -1,4 +1,5 @@
1
1
  import { createPluginFrameClient } from "@aiot-nexus/plugin-sdk/browser";
2
+ import type { PluginThemeTokens } from "@aiot-nexus/plugin-sdk";
2
3
 
3
4
  type DatabaseSource = "default" | "external";
4
5
  type SqliteAffinity = "integer" | "real" | "text" | "blob" | "numeric";
@@ -906,9 +907,11 @@ function applyLanguage(nextLanguage: SupportedLanguage): void {
906
907
  client.notifyResize();
907
908
  }
908
909
 
909
- function applyContext(context: { language: string; themeMode: ThemeMode }): void {
910
+ function applyContext(context: { language: string; themeMode: ThemeMode; theme?: { light: PluginThemeTokens; dark: PluginThemeTokens } }): void {
910
911
  applyLanguage(resolveLanguage(context.language));
911
912
  applyTheme(context.themeMode);
913
+ const resolvedMode = context.themeMode === "dark" || (context.themeMode === "system" && systemDarkMode.matches) ? "dark" : "light";
914
+ document.documentElement.dataset.hostPrimary = context.theme?.[resolvedMode].primary || "";
912
915
  revealPluginContent();
913
916
  }
914
917
 
@@ -1,8 +1,29 @@
1
1
  :root {
2
+ --bg: var(--nexus-color-background, #eef1f4);
3
+ --panel: var(--nexus-color-surface, #ffffff);
4
+ --raise: var(--nexus-color-surface-raised, #f7f9fa);
5
+ --hover: var(--nexus-color-surface-hover, #eef3f6);
6
+ --inset: var(--nexus-color-surface-inset, #e4e9ed);
7
+ --line: var(--nexus-color-border, #d7dde3);
8
+ --line-strong: var(--nexus-color-border-strong, #c2cbd3);
9
+ --fg: var(--nexus-color-text, #212a32);
10
+ --fg-dim: var(--nexus-color-text-muted, #5a6772);
11
+ --fg-faint: var(--nexus-color-text-faint, #8b97a1);
12
+ --brand: var(--nexus-color-primary, #8fc31f);
13
+ --brand-text: var(--nexus-color-accent, #6f9a15);
14
+ --on-brand: var(--nexus-color-on-primary, #ffffff);
15
+ --primary-action: var(--nexus-color-primary, #8fc31f);
16
+ --on-primary: var(--nexus-color-on-primary, #ffffff);
17
+ --on-negative: var(--nexus-color-on-negative, #ffffff);
18
+ --focus-ring: var(--nexus-color-accent, #6f9a15);
19
+ --brand-soft: var(--nexus-color-primary-soft, rgba(143, 195, 31, 0.12));
20
+ --negative: var(--nexus-color-negative, #c2402f);
2
21
  color-scheme: light;
3
- font-family: "Segoe UI", Arial, sans-serif;
4
- background: #f3f5f7;
5
- color: #20252b;
22
+ font-family: "Noto Sans SC", "Source Han Sans SC", "Microsoft YaHei",
23
+ "Segoe UI", system-ui, sans-serif;
24
+ background: var(--bg);
25
+ color: var(--fg);
26
+ font-size: 14px;
6
27
  }
7
28
 
8
29
  * {
@@ -19,7 +40,8 @@ body {
19
40
  height: 100vh;
20
41
  min-height: 100vh;
21
42
  overflow: hidden;
22
- background: #f3f5f7;
43
+ background: var(--bg);
44
+ color: var(--fg);
23
45
  }
24
46
 
25
47
  button,
@@ -46,9 +68,9 @@ textarea {
46
68
  .editor-panel,
47
69
  .data-panel {
48
70
  min-width: 0;
49
- border: 1px solid #d4d9df;
50
- border-radius: 8px;
51
- background: #ffffff;
71
+ border: 1px solid var(--line);
72
+ border-radius: 10px;
73
+ background: var(--panel);
52
74
  }
53
75
 
54
76
  .plugin-header {
@@ -86,18 +108,19 @@ p {
86
108
  }
87
109
 
88
110
  h1 {
89
- font-size: 20px;
111
+ font-size: 18px;
112
+ font-weight: 600;
90
113
  }
91
114
 
92
115
  h2 {
93
- font-size: 15px;
116
+ font-size: 14.5px;
94
117
  }
95
118
 
96
119
  .database-heading p {
97
120
  overflow: hidden;
98
121
  max-width: min(62vw, 760px);
99
122
  margin-top: 3px;
100
- color: #65717e;
123
+ color: var(--fg-dim);
101
124
  font-size: 12px;
102
125
  text-overflow: ellipsis;
103
126
  white-space: nowrap;
@@ -115,13 +138,15 @@ h2 {
115
138
 
116
139
  button {
117
140
  min-height: 32px;
118
- border: 1px solid #1768a6;
119
- border-radius: 6px;
141
+ border: 1px solid var(--brand);
142
+ border-radius: 7px;
120
143
  padding: 0 11px;
121
- background: #1768a6;
122
- color: #ffffff;
144
+ background: var(--brand);
145
+ color: var(--on-brand);
123
146
  font-weight: 600;
124
147
  cursor: pointer;
148
+ transition: filter 0.15s ease, background-color 0.15s ease,
149
+ border-color 0.15s ease, color 0.15s ease;
125
150
  }
126
151
 
127
152
  button:disabled,
@@ -133,14 +158,50 @@ textarea:disabled {
133
158
  }
134
159
 
135
160
  button.button-secondary {
136
- border-color: #aeb7c2;
137
- background: #ffffff;
138
- color: #20252b;
161
+ border-color: var(--line-strong);
162
+ background: var(--panel);
163
+ color: var(--fg);
139
164
  }
140
165
 
141
166
  button.button-danger {
142
- border-color: #b42318;
143
- background: #b42318;
167
+ border-color: var(--negative);
168
+ background: var(--negative);
169
+ color: var(--on-negative);
170
+ }
171
+
172
+ button:not(:disabled):hover {
173
+ border-color: var(--primary-action);
174
+ background: var(--primary-action);
175
+ color: var(--on-primary);
176
+ }
177
+
178
+ button.button-secondary:not(:disabled):hover,
179
+ button.record-action:not(:disabled):hover {
180
+ filter: none;
181
+ border-color: var(--brand);
182
+ background: var(--hover);
183
+ color: var(--fg);
184
+ }
185
+
186
+ button.button-danger:not(:disabled):hover {
187
+ border-color: var(--negative);
188
+ background: var(--negative);
189
+ color: var(--on-negative);
190
+ filter: brightness(1.06);
191
+ }
192
+
193
+ button:focus-visible,
194
+ input:focus-visible,
195
+ select:focus-visible,
196
+ textarea:focus-visible {
197
+ outline: 2px solid var(--focus-ring);
198
+ outline-offset: 2px;
199
+ }
200
+
201
+ input:hover,
202
+ select:hover,
203
+ textarea:hover {
204
+ border-color: var(--brand);
144
205
  }
145
206
 
146
207
  .compact-button {
@@ -168,7 +229,7 @@ button.button-danger {
168
229
  #statusLog,
169
230
  .editor-panel .panel-heading p,
170
231
  .field-current {
171
- color: #65717e;
232
+ color: var(--fg-dim);
172
233
  font-size: 12px;
173
234
  }
174
235
 
@@ -184,11 +245,11 @@ select,
184
245
  textarea {
185
246
  width: 100%;
186
247
  min-width: 0;
187
- border: 1px solid #aeb7c2;
188
- border-radius: 6px;
248
+ border: 1px solid var(--line-strong);
249
+ border-radius: 7px;
189
250
  padding: 7px 9px;
190
- background: #ffffff;
191
- color: #20252b;
251
+ background: var(--panel);
252
+ color: var(--fg);
192
253
  }
193
254
 
194
255
  textarea {
@@ -236,7 +297,7 @@ textarea {
236
297
  gap: 6px 8px;
237
298
  align-items: end;
238
299
  min-width: 0;
239
- border-top: 1px solid #e7eaee;
300
+ border-top: 1px solid var(--line);
240
301
  padding-top: 8px;
241
302
  }
242
303
 
@@ -255,11 +316,11 @@ textarea {
255
316
  }
256
317
 
257
318
  .column-badge {
258
- border: 1px solid #ccd3da;
319
+ border: 1px solid var(--line);
259
320
  border-radius: 4px;
260
321
  padding: 1px 4px;
261
- background: #f7f8fa;
262
- color: #56616d;
322
+ background: var(--raise);
323
+ color: var(--fg-dim);
263
324
  font-size: 10px;
264
325
  font-weight: 600;
265
326
  }
@@ -282,7 +343,7 @@ textarea {
282
343
 
283
344
  .error {
284
345
  margin-top: 8px;
285
- color: #b42318;
346
+ color: var(--negative);
286
347
  font-size: 12px;
287
348
  }
288
349
 
@@ -306,9 +367,9 @@ textarea {
306
367
 
307
368
  .empty-state {
308
369
  padding: 18px;
309
- border: 1px dashed #aeb7c2;
310
- border-radius: 6px;
311
- color: #65717e;
370
+ border: 1px dashed var(--line-strong);
371
+ border-radius: 7px;
372
+ color: var(--fg-dim);
312
373
  text-align: center;
313
374
  }
314
375
 
@@ -318,8 +379,8 @@ textarea {
318
379
  min-height: 80px;
319
380
  max-width: 100%;
320
381
  overflow: auto;
321
- border: 1px solid #d4d9df;
322
- border-radius: 6px;
382
+ border: 1px solid var(--line);
383
+ border-radius: 7px;
323
384
  }
324
385
 
325
386
  table {
@@ -331,7 +392,7 @@ table {
331
392
  th,
332
393
  td {
333
394
  max-width: 360px;
334
- border-bottom: 1px solid #e7eaee;
395
+ border-bottom: 1px solid var(--line);
335
396
  padding: 7px 9px;
336
397
  text-align: left;
337
398
  vertical-align: top;
@@ -342,8 +403,8 @@ th {
342
403
  z-index: 1;
343
404
  top: 0;
344
405
  min-width: 120px;
345
- background: #f7f8fa;
346
- color: #56616d;
406
+ background: var(--raise);
407
+ color: var(--fg-dim);
347
408
  font-size: 12px;
348
409
  }
349
410
 
@@ -365,7 +426,7 @@ td {
365
426
  .cell-null,
366
427
  .cell-blob,
367
428
  .cell-truncated {
368
- color: #65717e;
429
+ color: var(--fg-dim);
369
430
  font-family: Consolas, monospace;
370
431
  font-size: 12px;
371
432
  }
@@ -379,7 +440,7 @@ td {
379
440
  td.row-actions-cell {
380
441
  position: sticky;
381
442
  right: 0;
382
- background: #ffffff;
443
+ background: var(--panel);
383
444
  }
384
445
 
385
446
  .pager {
@@ -388,7 +449,7 @@ td.row-actions-cell {
388
449
  justify-content: flex-end;
389
450
  gap: 12px;
390
451
  padding-top: 9px;
391
- color: #65717e;
452
+ color: var(--fg-dim);
392
453
  font-size: 12px;
393
454
  }
394
455
 
@@ -449,14 +510,31 @@ td.row-actions-cell {
449
510
  }
450
511
 
451
512
  :root[data-theme="dark"] {
513
+ --bg: var(--nexus-color-background, #101418);
514
+ --panel: var(--nexus-color-surface, #1b222a);
515
+ --raise: var(--nexus-color-surface-raised, #171c22);
516
+ --hover: var(--nexus-color-surface-hover, #222a34);
517
+ --inset: var(--nexus-color-surface-inset, #0c0f13);
518
+ --line: var(--nexus-color-border, #2a333e);
519
+ --line-strong: var(--nexus-color-border-strong, #38434f);
520
+ --fg: var(--nexus-color-text, #e2e8ee);
521
+ --fg-dim: var(--nexus-color-text-muted, #97a3af);
522
+ --fg-faint: var(--nexus-color-text-faint, #64707c);
523
+ --brand-text: var(--nexus-color-accent, #a5d43e);
524
+ --primary-action: var(--nexus-color-primary, #8fc31f);
525
+ --on-primary: var(--nexus-color-on-primary, #ffffff);
526
+ --on-negative: var(--nexus-color-on-negative, #101418);
527
+ --focus-ring: var(--nexus-color-accent, #a5d43e);
528
+ --brand-soft: var(--nexus-color-primary-soft, rgba(143, 195, 31, 0.14));
529
+ --negative: var(--nexus-color-negative, #e2604f);
452
530
  color-scheme: dark;
453
- background: #16191d;
454
- color: #edf0f2;
531
+ background: var(--bg);
532
+ color: var(--fg);
455
533
  }
456
534
 
457
535
  :root[data-theme="dark"] body {
458
- background: #16191d;
459
- color: #edf0f2;
536
+ background: var(--bg);
537
+ color: var(--fg);
460
538
  }
461
539
 
462
540
  :root[data-theme="dark"] .plugin-header,
@@ -464,8 +542,8 @@ td.row-actions-cell {
464
542
  :root[data-theme="dark"] .editor-panel,
465
543
  :root[data-theme="dark"] .data-panel,
466
544
  :root[data-theme="dark"] td.row-actions-cell {
467
- border-color: #454b52;
468
- background: #24282d;
545
+ border-color: var(--line);
546
+ background: var(--panel);
469
547
  }
470
548
 
471
549
  :root[data-theme="dark"] .database-heading p,
@@ -479,36 +557,36 @@ td.row-actions-cell {
479
557
  :root[data-theme="dark"] .cell-null,
480
558
  :root[data-theme="dark"] .cell-blob,
481
559
  :root[data-theme="dark"] .cell-truncated {
482
- color: #aeb7c2;
560
+ color: var(--fg-dim);
483
561
  }
484
562
 
485
563
  :root[data-theme="dark"] input,
486
564
  :root[data-theme="dark"] select,
487
565
  :root[data-theme="dark"] textarea,
488
566
  :root[data-theme="dark"] button.button-secondary {
489
- border-color: #59616a;
490
- background: #191c20;
491
- color: #edf0f2;
567
+ border-color: var(--line-strong);
568
+ background: var(--inset);
569
+ color: var(--fg);
492
570
  }
493
571
 
494
572
  :root[data-theme="dark"] .column-badge {
495
- border-color: #59616a;
496
- background: #30353b;
497
- color: #d2d8de;
573
+ border-color: var(--line-strong);
574
+ background: var(--raise);
575
+ color: var(--fg-dim);
498
576
  }
499
577
 
500
578
  :root[data-theme="dark"] .table-wrap,
501
579
  :root[data-theme="dark"] .empty-state,
502
580
  :root[data-theme="dark"] .editor-field {
503
- border-color: #454b52;
581
+ border-color: var(--line);
504
582
  }
505
583
 
506
584
  :root[data-theme="dark"] th {
507
- background: #2d3238;
508
- color: #d2d8de;
585
+ background: var(--raise);
586
+ color: var(--fg-dim);
509
587
  }
510
588
 
511
589
  :root[data-theme="dark"] th,
512
590
  :root[data-theme="dark"] td {
513
- border-bottom-color: #454b52;
591
+ border-bottom-color: var(--line);
514
592
  }