@rhizomatics/signalk-einklabel-plugin 0.9.0-beta1 → 0.9.0-beta2

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/dist/config.d.ts CHANGED
@@ -94,19 +94,18 @@ export interface PluginConfig {
94
94
  */
95
95
  export declare const BUNDLED_TEMPLATES_DIR: string;
96
96
  export declare function defaultConfig(): PluginConfig;
97
+ export declare function readCurrentConfig(app: ServerAPI): Partial<PluginConfig>;
97
98
  /**
98
- * `app.readPluginOptions()`/`savePluginOptions()` aren't actually symmetric despite what their doc
99
- * comments imply: signalk-server's `readPluginOptions()` returns the *whole* on-disk file
100
- * (`{ configuration, enabled, enableDebug, enableLogging }`), not just the `configuration` object
101
- * `savePluginOptions()` writes into - see signalk-server's `interfaces/plugins.ts`
102
- * (`appCopy.readPluginOptions = () => getPluginOptions(plugin.id)` vs.
103
- * `appCopy.savePluginOptions = (configuration, cb) => savePluginOptions(id, { ...getPluginOptions(id), configuration }, cb)`).
104
- * Spreading that return value straight back into a save call therefore nests the whole file one level
105
- * deeper inside its own `configuration` key every time (see `support/signalk-einklabel-plugin.json` and
106
- * `clearForceRepaint` in `./repaintScheduler.ts`). Unwrapping any such nesting and keeping only recognised
107
- * fields here means every save collapses back down instead of growing, self-healing an already-corrupted file.
99
+ * Actively rewrites the on-disk file once it's nested (see `readCurrentConfig`'s doc comment) -
100
+ * `readCurrentConfig` alone only self-heals in memory for callers that go through it, but the admin
101
+ * UI's own config-editing form round-trips whatever raw JSON it was handed verbatim, including a
102
+ * stray nested `configuration` key it never touches (no schema field maps to it) - so left alone,
103
+ * every future save from the UI keeps re-persisting that dead weight forever (see
104
+ * `support/signalk-einklabel-plugin.json`). Called once at plugin start, which - unlike
105
+ * `clearForceRepaint` - isn't gated on any device having `forceRepaint` set, so a nested file gets
106
+ * flattened even if nothing ever triggers that path.
108
107
  */
109
- export declare function readCurrentConfig(app: ServerAPI): Partial<PluginConfig>;
108
+ export declare function healNestedConfig(app: ServerAPI): void;
110
109
  /**
111
110
  * Resolves the user-facing `templatesDir` setting to an actual directory, mirroring
112
111
  * signalk-parquet's `outputDirectory` convention: empty means the default location, a relative
package/dist/config.js CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BUNDLED_TEMPLATES_DIR = exports.ALL_DEVICES = void 0;
4
4
  exports.defaultConfig = defaultConfig;
5
5
  exports.readCurrentConfig = readCurrentConfig;
6
+ exports.healNestedConfig = healNestedConfig;
6
7
  exports.resolveTemplatesDir = resolveTemplatesDir;
7
8
  exports.parseDevice = parseDevice;
8
9
  exports.resolveTemplatePath = resolveTemplatePath;
@@ -65,14 +66,21 @@ const PLUGIN_CONFIG_KEYS = [
65
66
  * `clearForceRepaint` in `./repaintScheduler.ts`). Unwrapping any such nesting and keeping only recognised
66
67
  * fields here means every save collapses back down instead of growing, self-healing an already-corrupted file.
67
68
  */
68
- function readCurrentConfig(app) {
69
- let raw = app.readPluginOptions();
69
+ function unwrapNestedConfiguration(raw) {
70
+ let levels = 0;
70
71
  while (raw &&
71
72
  typeof raw === "object" &&
72
73
  "configuration" in raw &&
73
74
  typeof raw.configuration === "object") {
74
75
  raw = raw.configuration;
76
+ levels++;
75
77
  }
78
+ // One level is the normal, expected shape signalk-server itself stores on disk (see the doc
79
+ // comment above) - only a *second* nested `configuration` key onwards is the corruption this
80
+ // guards against.
81
+ return { unwrapped: raw, wasNested: levels > 1 };
82
+ }
83
+ function pickKnownKeys(raw) {
76
84
  const result = {};
77
85
  if (raw && typeof raw === "object") {
78
86
  for (const key of PLUGIN_CONFIG_KEYS) {
@@ -83,6 +91,31 @@ function readCurrentConfig(app) {
83
91
  }
84
92
  return result;
85
93
  }
94
+ function readCurrentConfig(app) {
95
+ const { unwrapped } = unwrapNestedConfiguration(app.readPluginOptions());
96
+ return pickKnownKeys(unwrapped);
97
+ }
98
+ /**
99
+ * Actively rewrites the on-disk file once it's nested (see `readCurrentConfig`'s doc comment) -
100
+ * `readCurrentConfig` alone only self-heals in memory for callers that go through it, but the admin
101
+ * UI's own config-editing form round-trips whatever raw JSON it was handed verbatim, including a
102
+ * stray nested `configuration` key it never touches (no schema field maps to it) - so left alone,
103
+ * every future save from the UI keeps re-persisting that dead weight forever (see
104
+ * `support/signalk-einklabel-plugin.json`). Called once at plugin start, which - unlike
105
+ * `clearForceRepaint` - isn't gated on any device having `forceRepaint` set, so a nested file gets
106
+ * flattened even if nothing ever triggers that path.
107
+ */
108
+ function healNestedConfig(app) {
109
+ const { unwrapped, wasNested } = unwrapNestedConfiguration(app.readPluginOptions());
110
+ if (!wasNested)
111
+ return;
112
+ app.savePluginOptions(pickKnownKeys(unwrapped), (err) => {
113
+ if (err)
114
+ app.debug(`failed to clean up legacy nested plugin config: ${err.message}`);
115
+ else
116
+ app.debug("cleaned up a legacy nested plugin config file on disk");
117
+ });
118
+ }
86
119
  /**
87
120
  * Resolves the user-facing `templatesDir` setting to an actual directory, mirroring
88
121
  * signalk-parquet's `outputDirectory` convention: empty means the default location, a relative
package/dist/plugin.js CHANGED
@@ -49,6 +49,7 @@ function createPlugin(app) {
49
49
  ...config,
50
50
  };
51
51
  app.debug(`starting with ${pluginConfig.devices.length} configured device(s)`);
52
+ (0, config_1.healNestedConfig)(app);
52
53
  if (pluginConfig.scanOnStart) {
53
54
  void runStartupScan(app, pluginConfig.scanDurationSeconds);
54
55
  }
@@ -234,11 +234,22 @@ async function considerRepaint(app, config, device, target, state, getApiUrl) {
234
234
  });
235
235
  const rawContext = await assembleRawContext(app, apiUrl, bindings);
236
236
  const templateHash = hashTemplate(templateMtimeMs);
237
+ // Hashed from `rawContext`, not `renderContext` below - `meta.repainted` (and the rest of `meta`)
238
+ // is only added after this point, deliberately, so a template merely *displaying* the repaint
239
+ // timestamp doesn't perpetually invalidate its own dedup and force a repaint every check. A full
240
+ // paint flashes the whole panel several times, and there's no confirmed partial-refresh path on
241
+ // these devices - so a bound value simply being unchanged is worth trusting over any staleness in
242
+ // a displayed clock.
237
243
  const dataHash = hashData(rawContext);
238
244
  const stateKey = stateKeyFor(device.friendlyName, address);
239
245
  const previous = state[stateKey];
240
246
  const templateChanged = previous?.templateHash !== templateHash;
241
247
  const dataChanged = previous?.dataHash !== dataHash;
248
+ // The devices are battery-constrained, so a repaint is skipped whenever neither the template nor
249
+ // the bound data has changed since the last successful paint - regardless of what triggered this
250
+ // check (interval or subscription) or whether it's a regular scheduled tick vs. the deferred
251
+ // startup catch-up (see `startupCheckTimer`). `forceRepaint` is the explicit, one-shot override
252
+ // for "repaint anyway".
242
253
  if (!templateChanged && !dataChanged && !device.forceRepaint) {
243
254
  app.debug(`${label}: data unchanged, skipping repaint`);
244
255
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhizomatics/signalk-einklabel-plugin",
3
- "version": "0.9.0-beta1",
3
+ "version": "0.9.0-beta2",
4
4
  "description": "Display SignalK data on eInk Electronic Shelf Labels",
5
5
  "keywords": [
6
6
  "ble",