retold-data-service 2.1.0 → 2.1.2

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.
Files changed (25) hide show
  1. package/.github/workflows/publish-image.yml +66 -0
  2. package/BUILDING-AND-PUBLISHING.md +140 -0
  3. package/Dockerfile +21 -0
  4. package/package.json +25 -16
  5. package/source/services/comprehension-loader/pict-app/providers/Pict-Provider-ComprehensionLoader.js +2 -2
  6. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Layout.js +43 -43
  7. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Load.js +14 -14
  8. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Schema.js +3 -3
  9. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Session.js +2 -2
  10. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Source.js +7 -7
  11. package/source/services/comprehension-loader/web/comprehension-loader.js +77 -77
  12. package/source/services/comprehension-loader/web/comprehension-loader.min.js +1 -1
  13. package/source/services/data-cloner/pict-app/providers/Pict-Provider-DataCloner.js +3 -3
  14. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Connection.js +1 -1
  15. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Deploy.js +5 -5
  16. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Export.js +11 -11
  17. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Layout.js +53 -53
  18. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Schema.js +3 -3
  19. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Session.js +2 -2
  20. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Sync.js +22 -22
  21. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-ViewData.js +4 -4
  22. package/source/services/data-cloner/web/data-cloner.js +110 -108
  23. package/source/services/data-cloner/web/data-cloner.js.map +1 -1
  24. package/source/services/data-cloner/web/data-cloner.min.js +1 -1
  25. package/source/services/data-cloner/web/data-cloner.min.js.map +1 -1
@@ -0,0 +1,66 @@
1
+ # Publish a container image to GitHub Container Registry on every
2
+ # version tag push (e.g. `v1.2.3`). Generated by `quack docker-init`.
3
+ #
4
+ # Image lands at:
5
+ # ghcr.io/stevenvelozo/retold-data-service:<version>
6
+ # ghcr.io/stevenvelozo/retold-data-service:latest (only on stable tags)
7
+
8
+ name: Publish container image
9
+
10
+ on:
11
+ push:
12
+ tags:
13
+ - 'v*.*.*'
14
+ workflow_dispatch:
15
+ inputs:
16
+ tag:
17
+ description: 'Tag to apply (e.g. dev or 1.2.3-test). `latest` is reserved for stable tag pushes.'
18
+ required: true
19
+ default: 'dev'
20
+
21
+ permissions:
22
+ contents: read
23
+ packages: write
24
+
25
+ jobs:
26
+ build-and-push:
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - name: Checkout
30
+ uses: actions/checkout@v4
31
+
32
+ - name: Set up QEMU (multi-arch support)
33
+ uses: docker/setup-qemu-action@v3
34
+
35
+ - name: Set up Docker Buildx
36
+ uses: docker/setup-buildx-action@v3
37
+
38
+ - name: Log in to GHCR
39
+ uses: docker/login-action@v3
40
+ with:
41
+ registry: ghcr.io
42
+ username: ${{ github.actor }}
43
+ password: ${{ secrets.GITHUB_TOKEN }}
44
+
45
+ - name: Compute image tags
46
+ id: meta
47
+ uses: docker/metadata-action@v5
48
+ with:
49
+ images: ghcr.io/${{ github.repository_owner }}/retold-data-service
50
+ tags: |
51
+ type=semver,pattern={{version}}
52
+ type=semver,pattern={{major}}.{{minor}}
53
+ type=semver,pattern={{major}}
54
+ type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
55
+
56
+ - name: Build and push
57
+ uses: docker/build-push-action@v5
58
+ with:
59
+ context: .
60
+ file: ./Dockerfile
61
+ platforms: linux/amd64,linux/arm64
62
+ push: true
63
+ tags: ${{ steps.meta.outputs.tags }}
64
+ labels: ${{ steps.meta.outputs.labels }}
65
+ cache-from: type=gha
66
+ cache-to: type=gha,mode=max
@@ -0,0 +1,140 @@
1
+ # Building and Publishing
2
+
3
+ How to ship `retold-data-service` to npm and to GitHub Container Registry
4
+ (GHCR). Generated by `quack docker-init`; the structure matches the
5
+ shared template across all dockerized retold tools.
6
+
7
+ `retold-data-service` is a **long-running service**. Restart policy in compose / k8s should be `unless-stopped` (or equivalent). The Dockerfile should declare a HEALTHCHECK against the service's health endpoint.
8
+
9
+ ---
10
+
11
+ ## TL;DR
12
+
13
+ ```bash
14
+ # npm-only release (the default — most common case)
15
+ npm run release:patch
16
+
17
+ # npm release that ALSO rebuilds the GHCR image
18
+ npm run release:patch:image
19
+ ```
20
+
21
+ The default release is npm-only. Docker images are deliberate, opt-in
22
+ artifacts because each multi-arch build burns several minutes of CI
23
+ time. Use `:image` (or set `BUILD_DOCKER=1`) when runtime code,
24
+ dependencies, env-var contract, or the Dockerfile changed.
25
+
26
+ ---
27
+
28
+ ## Prerequisites (one-time setup)
29
+
30
+ - **npm login** — `npm whoami` should print your username.
31
+ - **Git remote configured** — `git remote get-url origin` should print
32
+ `git@github.com:stevenvelozo/retold-data-service.git` (or the
33
+ HTTPS equivalent).
34
+ - **Push access to the repo** — required so `postversion` /
35
+ `postpublish` hooks can push commits and tags.
36
+ - **Docker** (only if you want to test the image locally before tag).
37
+
38
+ ---
39
+
40
+ ## Ecosystem convention: lockfiles are gitignored
41
+
42
+ `package-lock.json` is in this repo's `.gitignore` (Quackage convention
43
+ shared across the retold ecosystem). The Dockerfile must use `npm install`,
44
+ not `npm ci` — `npm ci` requires the lockfile to be in the build context
45
+ and CI runners only check out what's in git. If you see EUSAGE errors in
46
+ GHCR build logs, change `RUN npm ci` to `RUN npm install` in the
47
+ Dockerfile.
48
+
49
+ ---
50
+
51
+ ## Releasing
52
+
53
+ | Command | npm registry | GHCR image rebuild |
54
+ |--------------------------------------|--------------|--------------------|
55
+ | `npm run release:patch` | yes | no |
56
+ | `npm run release:patch:image` | yes | yes |
57
+ | `npm run release:minor` | yes | no |
58
+ | `npm run release:minor:image` | yes | yes |
59
+ | `npm run release:major` | yes | no |
60
+ | `npm run release:major:image` | yes | yes |
61
+
62
+ The non-`:image` variants are the default because most patch releases
63
+ don't change runtime behavior. The `:image` variants tell the pipeline
64
+ "this release does change runtime — build me a new image."
65
+
66
+ ### Direct CLI (also works)
67
+
68
+ ```bash
69
+ npm publish # npm only
70
+ npm run publish:docker # npm + docker (sets BUILD_DOCKER=1)
71
+ ```
72
+
73
+ ### From `retold-manager` TUI
74
+
75
+ - `[!]` Publish — npm only
76
+ - `[D]` Publish with docker image — npm + GHCR build
77
+
78
+ ### Promoting a previous npm release to docker later
79
+
80
+ If you released `v<x>` to npm only, then later decide you do want a
81
+ docker image:
82
+
83
+ ```bash
84
+ git push origin v<x> # pushes the local tag → GHCR fires
85
+ ```
86
+
87
+ The local tag is still sitting there from the original `npm version`
88
+ step. Pushing it triggers the workflow without touching npm.
89
+
90
+ ---
91
+
92
+ ## The chain
93
+
94
+ The lifecycle hooks all live in `package.json` and delegate to
95
+ `npx quack release …`. Default path (`BUILD_DOCKER` unset):
96
+
97
+ ```
98
+ npm publish
99
+ → prepublishOnly: npm test ← test gate
100
+ → publish to npm
101
+ → postpublish: BUILD_DOCKER unset → no-op ← image NOT triggered
102
+ ```
103
+
104
+ Docker-included path (`BUILD_DOCKER=1`):
105
+
106
+ ```
107
+ BUILD_DOCKER=1 npm publish (or: npm run publish:docker)
108
+ → prepublishOnly: npm test
109
+ → publish to npm
110
+ → postpublish: BUILD_DOCKER=1 → tag + push ← image trigger
111
+ → .github/workflows/publish-image.yml fires
112
+ → docker buildx build linux/amd64,linux/arm64
113
+ → docker push ghcr.io/stevenvelozo/retold-data-service:<version>
114
+ ```
115
+
116
+ ---
117
+
118
+ ## Verifying a release
119
+
120
+ 1. **npm**: `npm view retold-data-service version`
121
+ 2. **Workflow**: `https://github.com/stevenvelozo/retold-data-service/actions`
122
+ 3. **Image**: `docker pull ghcr.io/stevenvelozo/retold-data-service:latest`
123
+
124
+ If the first `docker pull` returns `denied`, the package is private by
125
+ default — flip visibility to public via Package Settings → Danger Zone
126
+ on the package page.
127
+
128
+ ---
129
+
130
+ ## Image consumption
131
+
132
+ ```bash
133
+ docker pull ghcr.io/stevenvelozo/retold-data-service:latest
134
+ docker run --rm ghcr.io/stevenvelozo/retold-data-service:latest
135
+ ```
136
+
137
+ Configuration via env vars: see this module's README for the supported
138
+ `<MODULE>_*` variables. Any secret-bearing var also accepts `<NAME>_FILE`
139
+ pointing at a file whose contents become the value (mysql/postgres
140
+ convention; works with docker secrets and k8s Secrets).
package/Dockerfile ADDED
@@ -0,0 +1,21 @@
1
+ FROM node:20-bookworm AS base
2
+ MAINTAINER steven velozo <steven@velozo.com>
3
+
4
+ RUN apt-get update && apt-get -y --force-yes install curl vim nano less \
5
+ tmux uuid-runtime
6
+
7
+ ADD package.json /service_root/package.json
8
+ RUN cd /service_root && npm install --omit=dev
9
+
10
+ ADD source /service_root/source
11
+ ADD bin /service_root/bin
12
+
13
+ WORKDIR /service_root
14
+
15
+ RUN rm -rf package-lock.json .git test
16
+
17
+ FROM base AS production
18
+
19
+ RUN date -u +"%Y-%m-%dT%H:%M:%SZ" > ./build.date
20
+
21
+ CMD ["node", "bin/retold-data-service-clone.js"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "retold-data-service",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "Serve up a whole model!",
5
5
  "main": "source/Retold-Data-Service.js",
6
6
  "bin": {
@@ -22,7 +22,16 @@
22
22
  "docker-service-shell": "docker exec -it retold-data-service /bin/bash",
23
23
  "test:integration": "node test/run-integration-tests.js",
24
24
  "test:integration:no-browser": "node test/run-integration-tests.js --skip-puppeteer",
25
- "test:all": "npx quack test && node test/run-integration-tests.js --skip-puppeteer"
25
+ "test:all": "npx quack test && node test/run-integration-tests.js --skip-puppeteer",
26
+ "postversion": "npx quack release postversion",
27
+ "postpublish": "npx quack release postpublish",
28
+ "publish:docker": "npx quack release publish --image",
29
+ "release:patch": "npx quack release patch",
30
+ "release:minor": "npx quack release minor",
31
+ "release:major": "npx quack release major",
32
+ "release:patch:image": "npx quack release patch --image",
33
+ "release:minor:image": "npx quack release minor --image",
34
+ "release:major:image": "npx quack release major --image"
26
35
  },
27
36
  "mocha": {
28
37
  "spec": "test/RetoldDataService_tests.js",
@@ -62,26 +71,26 @@
62
71
  "meadow-connection-sqlite": "^1.0.19",
63
72
  "pict-docuserve": "^0.1.5",
64
73
  "puppeteer": "^24.40.0",
65
- "quackage": "^1.1.2",
74
+ "quackage": "^1.2.3",
66
75
  "stricture": "^4.0.2",
67
76
  "supertest": "^7.2.2"
68
77
  },
69
78
  "dependencies": {
70
- "bibliograph": "^0.1.6",
71
- "fable": "^3.1.71",
79
+ "bibliograph": "^0.1.7",
80
+ "fable": "^3.1.72",
72
81
  "fable-serviceproviderbase": "^3.0.19",
73
- "meadow": "^2.0.37",
74
- "meadow-connection-manager": "^1.1.0",
75
- "pict-section-connection-form": "^0.0.1",
76
- "meadow-connection-mysql": "^1.0.18",
77
- "meadow-endpoints": "^4.0.17",
78
- "meadow-integration": "^1.0.38",
79
- "meadow-migrationmanager": "^0.0.14",
80
- "orator": "^6.1.0",
82
+ "meadow": "^2.0.38",
83
+ "meadow-connection-manager": "^1.1.2",
84
+ "pict-section-connection-form": "^0.0.2",
85
+ "meadow-connection-mysql": "^1.0.19",
86
+ "meadow-endpoints": "^4.0.20",
87
+ "meadow-integration": "^1.0.40",
88
+ "meadow-migrationmanager": "^0.0.16",
89
+ "orator": "^6.1.2",
81
90
  "orator-http-proxy": "^1.0.5",
82
- "orator-serviceserver-restify": "^2.0.10",
83
- "orator-static-server": "^2.1.3",
84
- "pict": "^1.0.365",
91
+ "orator-serviceserver-restify": "^2.0.11",
92
+ "orator-static-server": "^2.1.4",
93
+ "pict": "^1.0.366",
85
94
  "pict-section-histogram": "^1.0.0",
86
95
  "pict-sessionmanager": "^1.0.2",
87
96
  "stricture": "^4.0.2"
@@ -580,11 +580,11 @@ class ComprehensionLoaderProvider extends libPictProvider
580
580
  {
581
581
  if (tmpIsLive)
582
582
  {
583
- tmpHtml = '<div style="font-size:0.9em; color:#888; padding:8px 0">Load in progress, waiting for entity data\u2026</div>';
583
+ tmpHtml = '<div style="font-size:0.9em; color:var(--theme-color-text-muted, #888); padding:8px 0">Load in progress, waiting for entity data\u2026</div>';
584
584
  }
585
585
  else
586
586
  {
587
- tmpHtml = '<div style="font-size:0.9em; color:#888; padding:8px 0">No load data available. Run a load to see operation details here.</div>';
587
+ tmpHtml = '<div style="font-size:0.9em; color:var(--theme-color-text-muted, #888); padding:8px 0">No load data available. Run a load to see operation details here.</div>';
588
588
  }
589
589
  }
590
590
 
@@ -84,11 +84,11 @@ module.exports.default_configuration =
84
84
  DefaultDestinationAddress: '#ComprehensionLoader-Application-Container',
85
85
  CSS: /*css*/`
86
86
  * { box-sizing: border-box; margin: 0; padding: 0; }
87
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 20px; }
88
- h1 { margin-bottom: 20px; color: #1a1a1a; }
89
- h2 { margin-bottom: 12px; color: #444; font-size: 1.2em; border-bottom: 2px solid #ddd; padding-bottom: 6px; }
87
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--theme-color-background-secondary, #f5f5f5); color: var(--theme-color-text-primary, #333); padding: 20px; }
88
+ h1 { margin-bottom: 20px; color: var(--theme-color-text-primary, #1a1a1a); }
89
+ h2 { margin-bottom: 12px; color: var(--theme-color-text-secondary, #444); font-size: 1.2em; border-bottom: 2px solid var(--theme-color-border-default, #ddd); padding-bottom: 6px; }
90
90
 
91
- .section { background: #fff; border-radius: 8px; padding: 20px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
91
+ .section { background: var(--theme-color-background-panel, #fff); border-radius: 8px; padding: 20px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
92
92
 
93
93
  /* Accordion layout */
94
94
  .accordion-row { display: flex; gap: 0; margin-bottom: 16px; align-items: stretch; }
@@ -98,25 +98,25 @@ h2 { margin-bottom: 12px; color: #444; font-size: 1.2em; border-bottom: 2px soli
98
98
  user-select: none;
99
99
  }
100
100
  .accordion-card {
101
- flex: 1; background: #fff; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);
101
+ flex: 1; background: var(--theme-color-background-panel, #fff); border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);
102
102
  overflow: hidden; min-width: 0;
103
103
  }
104
104
  .accordion-header {
105
105
  display: flex; align-items: center; padding: 14px 20px; cursor: pointer;
106
106
  user-select: none; gap: 12px; transition: background 0.15s; line-height: 1.4;
107
107
  }
108
- .accordion-header:hover { background: #fafafa; }
109
- .accordion-title { font-weight: 600; color: #333; font-size: 1.05em; white-space: nowrap; }
110
- .accordion-preview { flex: 1; font-style: italic; color: #888; font-size: 0.9em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
108
+ .accordion-header:hover { background: var(--theme-color-background-secondary, #fafafa); }
109
+ .accordion-title { font-weight: 600; color: var(--theme-color-text-primary, #333); font-size: 1.05em; white-space: nowrap; }
110
+ .accordion-preview { flex: 1; font-style: italic; color: var(--theme-color-text-muted, #888); font-size: 0.9em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
111
111
  .accordion-toggle {
112
112
  flex: 0 0 20px; display: flex; align-items: center; justify-content: center;
113
- border-radius: 4px; transition: background 0.15s, transform 0.25s; font-size: 0.7em; color: #888;
113
+ border-radius: 4px; transition: background 0.15s, transform 0.25s; font-size: 0.7em; color: var(--theme-color-text-muted, #888);
114
114
  }
115
- .accordion-header:hover .accordion-toggle { background: #eee; color: #555; }
115
+ .accordion-header:hover .accordion-toggle { background: var(--theme-color-border-light, #eee); color: var(--theme-color-text-secondary, #555); }
116
116
  .accordion-card.open .accordion-toggle { transform: rotate(180deg); }
117
117
  .accordion-body { padding: 0 20px 20px; display: none; }
118
118
  .accordion-card.open .accordion-body { display: block; }
119
- .accordion-card.open .accordion-header { border-bottom: 1px solid #eee; }
119
+ .accordion-card.open .accordion-header { border-bottom: 1px solid var(--theme-color-border-light, #eee); }
120
120
  .accordion-card.open .accordion-preview { display: none; }
121
121
 
122
122
  /* Action controls (go link + auto checkbox) */
@@ -129,13 +129,13 @@ h2 { margin-bottom: 12px; color: #444; font-size: 1.2em; border-bottom: 2px soli
129
129
  }
130
130
  .accordion-go:hover { background: #e8f0fe; text-decoration: underline; }
131
131
  .accordion-auto {
132
- font-size: 0.82em; color: #999; white-space: nowrap; cursor: pointer;
132
+ font-size: 0.82em; color: var(--theme-color-text-muted, #999); white-space: nowrap; cursor: pointer;
133
133
  }
134
134
  .accordion-auto .auto-label { display: none; }
135
135
  .accordion-auto:hover .auto-label { display: inline; }
136
136
  .accordion-auto input[type="checkbox"] { width: auto; margin: 0; cursor: pointer; vertical-align: middle; position: relative; top: 0px; opacity: 0.75; transition: opacity 0.15s; }
137
137
  .accordion-auto:hover input[type="checkbox"] { opacity: 1; }
138
- .accordion-auto:hover { color: #666; }
138
+ .accordion-auto:hover { color: var(--theme-color-text-secondary, #666); }
139
139
 
140
140
  /* Phase status indicator */
141
141
  .accordion-phase {
@@ -143,12 +143,12 @@ h2 { margin-bottom: 12px; color: #444; font-size: 1.2em; border-bottom: 2px soli
143
143
  font-size: 0.85em; line-height: 1;
144
144
  }
145
145
  .accordion-phase.visible { display: flex; }
146
- .accordion-phase-ok { color: #28a745; }
146
+ .accordion-phase-ok { color: var(--theme-color-status-success, #28a745); }
147
147
  .accordion-phase-error { color: #dc3545; }
148
- .accordion-phase-busy { color: #28a745; }
148
+ .accordion-phase-busy { color: var(--theme-color-status-success, #28a745); }
149
149
  .accordion-phase-busy .phase-spinner {
150
150
  display: inline-block; width: 14px; height: 14px;
151
- border: 2px solid #28a745; border-top-color: transparent; border-radius: 50%;
151
+ border: 2px solid var(--theme-color-status-success, #28a745); border-top-color: transparent; border-radius: 50%;
152
152
  animation: phase-spin 0.8s linear infinite; vertical-align: middle;
153
153
  }
154
154
  @keyframes phase-spin {
@@ -160,13 +160,13 @@ h2 { margin-bottom: 12px; color: #444; font-size: 1.2em; border-bottom: 2px soli
160
160
  }
161
161
  .accordion-controls button {
162
162
  padding: 4px 10px; font-size: 0.82em; font-weight: 500; background: none;
163
- border: 1px solid #ccc; border-radius: 4px; color: #666; cursor: pointer; margin: 0;
163
+ border: 1px solid var(--theme-color-border-default, #ccc); border-radius: 4px; color: var(--theme-color-text-secondary, #666); cursor: pointer; margin: 0;
164
164
  }
165
- .accordion-controls button:hover { background: #f0f0f0; border-color: #aaa; color: #333; }
165
+ .accordion-controls button:hover { background: var(--theme-color-background-tertiary, #f0f0f0); border-color: var(--theme-color-text-muted, #aaa); color: var(--theme-color-text-primary, #333); }
166
166
 
167
167
  label { display: block; font-weight: 600; margin-bottom: 4px; font-size: 0.9em; }
168
168
  input[type="text"], input[type="password"], input[type="number"] {
169
- width: 100%; padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px;
169
+ width: 100%; padding: 8px 12px; border: 1px solid var(--theme-color-border-default, #ccc); border-radius: 4px;
170
170
  font-size: 0.95em; margin-bottom: 10px;
171
171
  }
172
172
  input[type="text"]:focus, input[type="password"]:focus, input[type="number"]:focus {
@@ -177,13 +177,13 @@ button {
177
177
  padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer;
178
178
  font-size: 0.9em; font-weight: 600; margin-right: 8px; margin-bottom: 8px;
179
179
  }
180
- button.primary { background: #4a90d9; color: #fff; }
180
+ button.primary { background: #4a90d9; color: var(--theme-color-background-panel, #fff); }
181
181
  button.primary:hover { background: #357abd; }
182
- button.secondary { background: #6c757d; color: #fff; }
182
+ button.secondary { background: #6c757d; color: var(--theme-color-background-panel, #fff); }
183
183
  button.secondary:hover { background: #5a6268; }
184
- button.danger { background: #dc3545; color: #fff; }
184
+ button.danger { background: #dc3545; color: var(--theme-color-background-panel, #fff); }
185
185
  button.danger:hover { background: #c82333; }
186
- button.success { background: #28a745; color: #fff; }
186
+ button.success { background: var(--theme-color-status-success, #28a745); color: var(--theme-color-background-panel, #fff); }
187
187
  button.success:hover { background: #218838; }
188
188
  button:disabled { opacity: 0.5; cursor: not-allowed; }
189
189
 
@@ -198,7 +198,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
198
198
 
199
199
  a { color: #4a90d9; }
200
200
 
201
- select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; font-size: 0.95em; margin-bottom: 10px; }
201
+ select { background: var(--theme-color-background-panel, #fff); width: 100%; padding: 8px 12px; border: 1px solid var(--theme-color-border-default, #ccc); border-radius: 4px; font-size: 0.95em; margin-bottom: 10px; }
202
202
 
203
203
  .checkbox-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
204
204
  .checkbox-row input[type="checkbox"] { width: auto; margin: 0; }
@@ -206,16 +206,16 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
206
206
 
207
207
  /* Live Status Bar */
208
208
  .live-status-bar {
209
- background: #fff; border-radius: 8px; margin-bottom: 16px;
209
+ background: var(--theme-color-background-panel, #fff); border-radius: 8px; margin-bottom: 16px;
210
210
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
211
211
  position: sticky; top: 0; z-index: 100; border-left: 4px solid #6c757d;
212
212
  }
213
213
  .live-status-bar.phase-idle { border-left-color: #6c757d; }
214
214
  .live-status-bar.phase-disconnected { border-left-color: #dc3545; }
215
215
  .live-status-bar.phase-ready { border-left-color: #4a90d9; }
216
- .live-status-bar.phase-loading { border-left-color: #28a745; }
216
+ .live-status-bar.phase-loading { border-left-color: var(--theme-color-status-success, #28a745); }
217
217
  .live-status-bar.phase-stopping { border-left-color: #ffc107; }
218
- .live-status-bar.phase-complete { border-left-color: #28a745; }
218
+ .live-status-bar.phase-complete { border-left-color: var(--theme-color-status-success, #28a745); }
219
219
 
220
220
  .live-status-dot {
221
221
  width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0;
@@ -225,34 +225,34 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
225
225
  .live-status-bar.phase-disconnected .live-status-dot { background: #dc3545; }
226
226
  .live-status-bar.phase-ready .live-status-dot { background: #4a90d9; }
227
227
  .live-status-bar.phase-loading .live-status-dot {
228
- background: #28a745;
228
+ background: var(--theme-color-status-success, #28a745);
229
229
  animation: live-pulse 1.5s ease-in-out infinite;
230
230
  }
231
231
  .live-status-bar.phase-stopping .live-status-dot {
232
232
  background: #ffc107;
233
233
  animation: live-pulse 0.8s ease-in-out infinite;
234
234
  }
235
- .live-status-bar.phase-complete .live-status-dot { background: #28a745; }
235
+ .live-status-bar.phase-complete .live-status-dot { background: var(--theme-color-status-success, #28a745); }
236
236
 
237
237
  @keyframes live-pulse {
238
238
  0%, 100% { opacity: 1; transform: scale(1); }
239
239
  50% { opacity: 0.4; transform: scale(0.8); }
240
240
  }
241
241
 
242
- .live-status-message { flex: 1; font-size: 0.92em; color: #333; line-height: 1.4; }
242
+ .live-status-message { flex: 1; font-size: 0.92em; color: var(--theme-color-text-primary, #333); line-height: 1.4; }
243
243
 
244
244
  .live-status-meta {
245
- display: flex; gap: 16px; flex-shrink: 0; font-size: 0.82em; color: #666;
245
+ display: flex; gap: 16px; flex-shrink: 0; font-size: 0.82em; color: var(--theme-color-text-secondary, #666);
246
246
  }
247
247
  .live-status-meta-item { white-space: nowrap; }
248
- .live-status-meta-item strong { color: #333; }
248
+ .live-status-meta-item strong { color: var(--theme-color-text-primary, #333); }
249
249
 
250
250
  .live-status-progress-bar {
251
251
  height: 3px; background: #e9ecef; border-radius: 2px; overflow: hidden;
252
252
  position: absolute; bottom: 0; left: 0; right: 0;
253
253
  }
254
254
  .live-status-progress-fill {
255
- height: 100%; background: #28a745; transition: width 1s ease;
255
+ height: 100%; background: var(--theme-color-status-success, #28a745); transition: width 1s ease;
256
256
  }
257
257
  /* Expandable status bar */
258
258
  .live-status-header {
@@ -264,7 +264,7 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
264
264
  }
265
265
  .live-status-expand-toggle {
266
266
  flex: 0 0 20px; display: flex; align-items: center; justify-content: center;
267
- font-size: 0.7em; color: #888; transition: transform 0.25s;
267
+ font-size: 0.7em; color: var(--theme-color-text-muted, #888); transition: transform 0.25s;
268
268
  }
269
269
  .live-status-bar.expanded .live-status-expand-toggle { transform: rotate(180deg); }
270
270
 
@@ -276,9 +276,9 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
276
276
  .status-detail-section { margin-bottom: 14px; }
277
277
  .status-detail-section:last-child { margin-bottom: 0; }
278
278
  .status-detail-section-title {
279
- font-size: 0.85em; font-weight: 600; color: #555; text-transform: uppercase;
279
+ font-size: 0.85em; font-weight: 600; color: var(--theme-color-text-secondary, #555); text-transform: uppercase;
280
280
  letter-spacing: 0.5px; margin-bottom: 8px; padding-bottom: 4px;
281
- border-bottom: 1px solid #eee;
281
+ border-bottom: 1px solid var(--theme-color-border-light, #eee);
282
282
  }
283
283
 
284
284
  /* Running Operations */
@@ -292,26 +292,26 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
292
292
  min-width: 120px;
293
293
  }
294
294
  .running-op-bar-fill { height: 100%; background: #4a90d9; transition: width 0.5s ease; }
295
- .running-op-count { font-size: 0.85em; color: #666; white-space: nowrap; }
296
- .running-op-pending { color: #888; font-size: 0.85em; font-style: italic; padding: 4px 0; }
295
+ .running-op-count { font-size: 0.85em; color: var(--theme-color-text-secondary, #666); white-space: nowrap; }
296
+ .running-op-pending { color: var(--theme-color-text-muted, #888); font-size: 0.85em; font-style: italic; padding: 4px 0; }
297
297
 
298
298
  /* Completed Operations */
299
- .completed-op-row { padding: 8px 0; border-bottom: 1px solid #f0f0f0; }
299
+ .completed-op-row { padding: 8px 0; border-bottom: 1px solid var(--theme-color-background-tertiary, #f0f0f0); }
300
300
  .completed-op-row:last-child { border-bottom: none; }
301
301
  .completed-op-header {
302
302
  display: flex; align-items: center; gap: 10px; font-size: 0.9em; margin-bottom: 4px;
303
303
  }
304
304
  .completed-op-name { font-weight: 600; }
305
- .completed-op-stats { color: #666; font-size: 0.85em; }
306
- .completed-op-checkmark { color: #28a745; }
305
+ .completed-op-stats { color: var(--theme-color-text-secondary, #666); font-size: 0.85em; }
306
+ .completed-op-checkmark { color: var(--theme-color-status-success, #28a745); }
307
307
 
308
308
  /* Error Operations */
309
- .error-op-row { padding: 6px 0; border-bottom: 1px solid #f0f0f0; font-size: 0.9em; }
309
+ .error-op-row { padding: 6px 0; border-bottom: 1px solid var(--theme-color-background-tertiary, #f0f0f0); font-size: 0.9em; }
310
310
  .error-op-row:last-child { border-bottom: none; }
311
311
  .error-op-header { display: flex; align-items: center; gap: 8px; }
312
312
  .error-op-name { font-weight: 600; color: #dc3545; }
313
313
  .error-op-status { font-size: 0.82em; color: #dc3545; }
314
- .error-op-message { font-size: 0.82em; color: #888; margin-top: 2px; padding-left: 18px; }
314
+ .error-op-message { font-size: 0.82em; color: var(--theme-color-text-muted, #888); margin-top: 2px; padding-left: 18px; }
315
315
  `,
316
316
  Templates:
317
317
  [
@@ -162,14 +162,14 @@ class ComprehensionLoaderLoadView extends libPictView
162
162
  + '<div class="report-card">'
163
163
  + ' <div class="card-label">Records Pushed</div>'
164
164
  + ' <div class="card-value">' + tmpTotalPushed + '</div>'
165
- + ' <div style="font-size:0.75em; color:#888">of ' + tmpTotalRecords + '</div>'
165
+ + ' <div style="font-size:0.75em; color:var(--theme-color-text-muted, #888)">of ' + tmpTotalRecords + '</div>'
166
166
  + '</div>';
167
167
 
168
168
  // Anomalies
169
169
  let tmpAnomalyContainer = document.getElementById('reportAnomalies');
170
170
  if (pReport.Anomalies.length === 0)
171
171
  {
172
- tmpAnomalyContainer.innerHTML = '<div style="color:#28a745; font-weight:600; font-size:0.9em">No anomalies detected.</div>';
172
+ tmpAnomalyContainer.innerHTML = '<div style="color:var(--theme-color-status-success, #28a745); font-weight:600; font-size:0.9em">No anomalies detected.</div>';
173
173
  }
174
174
  else
175
175
  {
@@ -194,7 +194,7 @@ class ComprehensionLoaderLoadView extends libPictView
194
194
  let tmpEntityContainer = document.getElementById('reportEntityDetails');
195
195
  if (pReport.Entities && pReport.Entities.length > 0)
196
196
  {
197
- let tmpHtml = '<h4 style="margin:0 0 8px; font-size:0.95em; color:#444">Entity Details</h4>';
197
+ let tmpHtml = '<h4 style="margin:0 0 8px; font-size:0.95em; color:var(--theme-color-text-secondary, #444)">Entity Details</h4>';
198
198
  tmpHtml += '<table class="progress-table">';
199
199
  tmpHtml += '<tr><th>Entity</th><th>Duration</th><th>Records</th><th>Status</th></tr>';
200
200
  for (let i = 0; i < pReport.Entities.length; i++)
@@ -394,22 +394,22 @@ module.exports.default_configuration =
394
394
  DefaultDestinationAddress: '#ComprehensionLoader-Section-Load',
395
395
  CSS: /*css*/`
396
396
  .progress-table { width: 100%; border-collapse: collapse; margin-top: 4px; margin-bottom: 4px; }
397
- .progress-table th, .progress-table td { text-align: left; padding: 6px 12px; border-bottom: 1px solid #eee; font-size: 0.9em; }
397
+ .progress-table th, .progress-table td { text-align: left; padding: 6px 12px; border-bottom: 1px solid var(--theme-color-border-light, #eee); font-size: 0.9em; }
398
398
  .progress-table th { background: #f8f9fa; font-weight: 600; }
399
- .progress-table-muted td { color: #888; padding: 3px 12px; font-size: 0.85em; border-bottom: 1px solid #f4f5f6; }
399
+ .progress-table-muted td { color: var(--theme-color-text-muted, #888); padding: 3px 12px; font-size: 0.85em; border-bottom: 1px solid #f4f5f6; }
400
400
  .progress-bar-container { width: 120px; height: 16px; background: #e9ecef; border-radius: 8px; overflow: hidden; display: inline-block; vertical-align: middle; }
401
- .progress-bar-fill { height: 100%; background: #28a745; transition: width 0.3s; }
402
- .sync-section-header { font-size: 0.8em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; color: #4a90d9; padding: 10px 0 2px 0; margin-top: 6px; border-top: 1px solid #e0e0e0; }
401
+ .progress-bar-fill { height: 100%; background: var(--theme-color-status-success, #28a745); transition: width 0.3s; }
402
+ .sync-section-header { font-size: 0.8em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; color: #4a90d9; padding: 10px 0 2px 0; margin-top: 6px; border-top: 1px solid var(--theme-color-border-default, #e0e0e0); }
403
403
  .sync-section-header:first-child { border-top: none; margin-top: 10px; }
404
404
  .sync-section-header-error { color: #dc3545; }
405
- .sync-section-header-ok { color: #28a745; }
406
- .sync-section-count { font-weight: 400; color: #999; font-size: 0.95em; }
407
- .sync-section-overflow { font-size: 0.8em; color: #aaa; padding: 2px 12px 6px; }
408
- .sync-pending-count { text-align: right; color: #aaa; font-size: 0.85em; }
405
+ .sync-section-header-ok { color: var(--theme-color-status-success, #28a745); }
406
+ .sync-section-count { font-weight: 400; color: var(--theme-color-text-muted, #999); font-size: 0.95em; }
407
+ .sync-section-overflow { font-size: 0.8em; color: var(--theme-color-text-muted, #aaa); padding: 2px 12px 6px; }
408
+ .sync-pending-count { text-align: right; color: var(--theme-color-text-muted, #aaa); font-size: 0.85em; }
409
409
  .report-card { background: #f8f9fa; border-radius: 8px; padding: 12px 16px; min-width: 140px; text-align: center; border: 1px solid #e9ecef; }
410
- .report-card .card-label { font-size: 0.8em; color: #666; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
410
+ .report-card .card-label { font-size: 0.8em; color: var(--theme-color-text-secondary, #666); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
411
411
  .report-card .card-value { font-size: 1.4em; font-weight: 700; }
412
- .report-card.outcome-success { border-left: 4px solid #28a745; }
412
+ .report-card.outcome-success { border-left: 4px solid var(--theme-color-status-success, #28a745); }
413
413
  .report-card.outcome-partial { border-left: 4px solid #ffc107; }
414
414
  .report-card.outcome-error { border-left: 4px solid #dc3545; }
415
415
  .report-card.outcome-stopped { border-left: 4px solid #6c757d; }
@@ -442,7 +442,7 @@ module.exports.default_configuration =
442
442
  <div id="loadProgress"></div>
443
443
 
444
444
  <!-- Load Report (appears after load completes) -->
445
- <div id="loadReportSection" style="display:none; margin-top:16px; padding-top:16px; border-top:2px solid #ddd">
445
+ <div id="loadReportSection" style="display:none; margin-top:16px; padding-top:16px; border-top:2px solid var(--theme-color-border-default, #ddd)">
446
446
  <h3 style="margin:0 0 12px; font-size:1.1em">Load Report</h3>
447
447
 
448
448
  <div id="reportSummaryCards" style="display:flex; gap:12px; flex-wrap:wrap; margin-bottom:16px"></div>
@@ -53,14 +53,14 @@ class ComprehensionLoaderSchemaView extends libPictView
53
53
 
54
54
  if (tmpEntities.length === 0)
55
55
  {
56
- tmpContainer.innerHTML = '<div style="color:#888; font-size:0.9em">No entities found.</div>';
56
+ tmpContainer.innerHTML = '<div style="color:var(--theme-color-text-muted, #888); font-size:0.9em">No entities found.</div>';
57
57
  return;
58
58
  }
59
59
 
60
- let tmpHtml = '<div style="font-size:0.9em; color:#555">';
60
+ let tmpHtml = '<div style="font-size:0.9em; color:var(--theme-color-text-secondary, #555)">';
61
61
  for (let i = 0; i < tmpEntities.length; i++)
62
62
  {
63
- tmpHtml += '<span style="display:inline-block; background:#f0f0f0; border-radius:4px; padding:2px 8px; margin:2px 4px 2px 0; font-size:0.9em">';
63
+ tmpHtml += '<span style="display:inline-block; background:var(--theme-color-background-tertiary, #f0f0f0); border-radius:4px; padding:2px 8px; margin:2px 4px 2px 0; font-size:0.9em">';
64
64
  tmpHtml += this.pict.providers.ComprehensionLoader.escapeHtml(tmpEntities[i]);
65
65
  tmpHtml += '</span>';
66
66
  }