retold-data-service 2.1.1 → 2.1.5

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 (89) hide show
  1. package/BUILDING-AND-PUBLISHING.md +2 -2
  2. package/Dockerfile +1 -1
  3. package/README.md +12 -27
  4. package/build-all.js +66 -0
  5. package/diagrams/architecture.excalidraw +2966 -0
  6. package/diagrams/architecture.mmd +17 -0
  7. package/diagrams/architecture.svg +2 -0
  8. package/docs/README.md +12 -12
  9. package/docs/_brand.json +18 -0
  10. package/docs/_cover.md +1 -1
  11. package/docs/_topbar.md +1 -1
  12. package/docs/_version.json +3 -3
  13. package/docs/api/reference.md +8 -8
  14. package/docs/architecture.md +6 -84
  15. package/docs/diagrams/component-diagram.excalidraw +2807 -0
  16. package/docs/diagrams/component-diagram.mmd +14 -0
  17. package/docs/diagrams/component-diagram.svg +2 -0
  18. package/docs/diagrams/component-stack.excalidraw +1169 -0
  19. package/docs/diagrams/component-stack.mmd +6 -0
  20. package/docs/diagrams/component-stack.svg +2 -0
  21. package/docs/diagrams/hook-execution-order.excalidraw +3230 -0
  22. package/docs/diagrams/hook-execution-order.mmd +19 -0
  23. package/docs/diagrams/hook-execution-order.svg +2 -0
  24. package/docs/diagrams/initialization-flow.excalidraw +1800 -0
  25. package/docs/diagrams/initialization-flow.mmd +22 -0
  26. package/docs/diagrams/initialization-flow.svg +2 -0
  27. package/docs/index.html +6 -7
  28. package/docs/lifecycle-hooks.md +2 -21
  29. package/docs/retold-catalog.json +141 -141
  30. package/docs/retold-keyword-index.json +6818 -1608
  31. package/package.json +130 -96
  32. package/source/services/RetoldDataService-Brand.js +13 -0
  33. package/source/services/comprehension-loader/pict-app/Pict-Application-ComprehensionLoader.js +65 -15
  34. package/source/services/comprehension-loader/pict-app/providers/Pict-Provider-ComprehensionLoader.js +2 -2
  35. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Layout.js +68 -114
  36. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Load.js +29 -29
  37. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Schema.js +3 -3
  38. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Session.js +2 -2
  39. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-SettingsPanel.js +62 -0
  40. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Shell.js +142 -0
  41. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Source.js +7 -7
  42. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-StatusBar.js +125 -0
  43. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-StatusDetail.js +89 -0
  44. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-TopBar-Nav.js +42 -0
  45. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-TopBar-User.js +48 -0
  46. package/source/services/comprehension-loader/web/comprehension-loader.js +5475 -6243
  47. package/source/services/comprehension-loader/web/comprehension-loader.js.map +1 -1
  48. package/source/services/comprehension-loader/web/comprehension-loader.min.js +75 -1
  49. package/source/services/comprehension-loader/web/comprehension-loader.min.js.map +1 -1
  50. package/source/services/comprehension-loader/web/favicons/favicon-dark.svg +13 -0
  51. package/source/services/comprehension-loader/web/favicons/favicon-light.svg +13 -0
  52. package/source/services/comprehension-loader/web/favicons/favicon.svg +13 -0
  53. package/source/services/comprehension-loader/web/index.html +3 -0
  54. package/source/services/comprehension-loader/web/pict.min.js +12 -0
  55. package/source/services/data-cloner/DataCloner-Command-Headless.js +2 -1
  56. package/source/services/data-cloner/DataCloner-Command-Sync.js +110 -75
  57. package/source/services/data-cloner/pict-app/Pict-Application-DataCloner.js +70 -47
  58. package/source/services/data-cloner/pict-app/providers/Pict-Provider-DataCloner.js +3 -3
  59. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Connection.js +1 -1
  60. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Deploy.js +5 -5
  61. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Export.js +11 -11
  62. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Layout.js +89 -135
  63. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Schema.js +3 -3
  64. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Session.js +2 -2
  65. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-SettingsPanel.js +61 -0
  66. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Shell.js +136 -0
  67. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-StatusBar.js +117 -0
  68. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-StatusDetail.js +81 -0
  69. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Sync.js +38 -38
  70. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-TopBar-Nav.js +42 -0
  71. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-TopBar-User.js +48 -0
  72. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-ViewData.js +5 -5
  73. package/source/services/data-cloner/web/data-cloner.js +5855 -8067
  74. package/source/services/data-cloner/web/data-cloner.js.map +1 -1
  75. package/source/services/data-cloner/web/data-cloner.min.js +75 -1
  76. package/source/services/data-cloner/web/data-cloner.min.js.map +1 -1
  77. package/source/services/data-cloner/web/favicons/favicon-dark.svg +13 -0
  78. package/source/services/data-cloner/web/favicons/favicon-light.svg +13 -0
  79. package/source/services/data-cloner/web/favicons/favicon.svg +13 -0
  80. package/source/services/data-cloner/web/favicons/favicons/favicon-dark.svg +13 -0
  81. package/source/services/data-cloner/web/favicons/favicons/favicon-light.svg +13 -0
  82. package/source/services/data-cloner/web/favicons/favicons/favicon.svg +13 -0
  83. package/source/services/data-cloner/web/index.html +3 -0
  84. package/source/services/data-cloner/web/pict.min.js +12 -0
  85. package/test/Bundles_smoke_tests.js +43 -0
  86. package/test/ComprehensionLoader_smoke_tests.js +95 -0
  87. package/test/DataCloner-RuntimeOverrides_tests.js +344 -0
  88. package/test/DataCloner_smoke_tests.js +87 -0
  89. package/docs/css/docuserve.css +0 -327
@@ -253,7 +253,8 @@ module.exports = (pDataClonerService, pConfig, pCLIOptions, fCallback) =>
253
253
  MaxRecordsPerEntity: tmpMaxRecords || tmpSync.MaxRecordsPerEntity || 0,
254
254
  DateTimePrecisionMS: tmpSync.DateTimePrecisionMS,
255
255
  BackSyncTimeLimit: tmpSync.BackSyncTimeLimit,
256
- TrueUpPageSize: tmpSync.TrueUpPageSize
256
+ TrueUpPageSize: tmpSync.TrueUpPageSize,
257
+ SyncEntityOptions: tmpSync.SyncEntityOptions
257
258
  });
258
259
 
259
260
  tmpFable.log.info(`Headless: Starting ${tmpSyncBody.SyncMode} sync...`);
@@ -10,6 +10,109 @@
10
10
  const libFableLog = require('fable-log');
11
11
  const libPath = require('path');
12
12
 
13
+ // Runtime-overridable sync properties for the /clone/sync/start route.
14
+ // Each entry is a coerce function returning the value to apply, or null
15
+ // if the input is invalid. Drives both the top-level body overrides and
16
+ // the per-entity `SyncEntityOptions` overrides — adding a new knob is a
17
+ // one-line change here.
18
+ const _RuntimeSyncProperties = {
19
+ BackSyncTimeLimit: (pVal) => { let tmpN = parseInt(pVal, 10); return (!isNaN(tmpN) && tmpN > 0) ? tmpN : null; },
20
+ MaxRecordsPerEntity: (pVal) => { let tmpN = parseInt(pVal, 10); return (!isNaN(tmpN) && tmpN > 0) ? tmpN : null; },
21
+ DateTimePrecisionMS: (pVal) => { let tmpN = parseInt(pVal, 10); return !isNaN(tmpN) ? tmpN : null; },
22
+ TrueUpPageSize: (pVal) => { let tmpN = parseInt(pVal, 10); return (!isNaN(tmpN) && tmpN > 0) ? tmpN : null; },
23
+ UseAdvancedIDPagination: (pVal) => !!pVal,
24
+ SyncDeletedRecords: (pVal) => !!pVal
25
+ };
26
+
27
+ /**
28
+ * Apply runtime sync overrides from a request body to a live MeadowSync.
29
+ *
30
+ * Mutates `pMeadowSync.MeadowSyncEntities[*]` properties, plus mirrors the
31
+ * "base" globals onto the MeadowSync orchestrator and `cloneState.SyncDeletedRecords`.
32
+ * Top-level body keys form the base config; per-entity overrides under
33
+ * `pBody.SyncEntityOptions[<TableName>]` are layered on top (per-entity wins).
34
+ *
35
+ * Only properties listed in `_RuntimeSyncProperties` are honored; unknown keys
36
+ * and invalid values are silently ignored. Unknown entity names produce a warn.
37
+ *
38
+ * @param {Object} pBody - Request body (or any plain object with the same shape).
39
+ * @param {Object} pMeadowSync - MeadowSync orchestrator instance with `MeadowSyncEntities`.
40
+ * @param {Object} pCloneState - Clone-state object whose `SyncDeletedRecords` mirrors the base.
41
+ * @param {Object} pLog - Logger with `info(msg)` and `warn(msg)`.
42
+ */
43
+ const applyRuntimeSyncOverrides = (pBody, pMeadowSync, pCloneState, pLog) =>
44
+ {
45
+ // Compute the base runtime config from top-level body keys.
46
+ let tmpBase = {};
47
+ let tmpBaseSchemaKeys = Object.keys(_RuntimeSyncProperties);
48
+ for (let i = 0; i < tmpBaseSchemaKeys.length; i++)
49
+ {
50
+ let tmpKey = tmpBaseSchemaKeys[i];
51
+ if (!pBody.hasOwnProperty(tmpKey)) continue;
52
+ let tmpCoerced = _RuntimeSyncProperties[tmpKey](pBody[tmpKey]);
53
+ if (tmpCoerced === null) continue;
54
+ tmpBase[tmpKey] = tmpCoerced;
55
+ }
56
+
57
+ let tmpPerEntity = (pBody.SyncEntityOptions && typeof(pBody.SyncEntityOptions) === 'object') ? pBody.SyncEntityOptions : {};
58
+
59
+ // Mirror base values onto the MeadowSync orchestrator + cloneState.
60
+ let tmpBaseKeys = Object.keys(tmpBase);
61
+ for (let i = 0; i < tmpBaseKeys.length; i++)
62
+ {
63
+ if (pMeadowSync.hasOwnProperty(tmpBaseKeys[i]))
64
+ {
65
+ pMeadowSync[tmpBaseKeys[i]] = tmpBase[tmpBaseKeys[i]];
66
+ }
67
+ }
68
+ if (tmpBase.hasOwnProperty('SyncDeletedRecords'))
69
+ {
70
+ pCloneState.SyncDeletedRecords = tmpBase.SyncDeletedRecords;
71
+ }
72
+
73
+ // Apply base to every entity.
74
+ let tmpAllEntityNames = Object.keys(pMeadowSync.MeadowSyncEntities || {});
75
+ for (let i = 0; i < tmpAllEntityNames.length; i++)
76
+ {
77
+ let tmpEntity = pMeadowSync.MeadowSyncEntities[tmpAllEntityNames[i]];
78
+ for (let j = 0; j < tmpBaseKeys.length; j++)
79
+ {
80
+ tmpEntity[tmpBaseKeys[j]] = tmpBase[tmpBaseKeys[j]];
81
+ }
82
+ }
83
+
84
+ // Layer per-entity overrides on top. Log per entity that actually had
85
+ // overrides applied; warn on unknown entity names.
86
+ let tmpPerEntityNames = Object.keys(tmpPerEntity);
87
+ for (let i = 0; i < tmpPerEntityNames.length; i++)
88
+ {
89
+ let tmpEntityName = tmpPerEntityNames[i];
90
+ let tmpEntity = pMeadowSync.MeadowSyncEntities && pMeadowSync.MeadowSyncEntities[tmpEntityName];
91
+ if (!tmpEntity)
92
+ {
93
+ pLog.warn(`SyncEntityOptions: no sync entity ${tmpEntityName} loaded; ignoring overrides for it.`);
94
+ continue;
95
+ }
96
+ let tmpOverrides = tmpPerEntity[tmpEntityName];
97
+ if (!tmpOverrides || typeof(tmpOverrides) !== 'object') continue;
98
+ let tmpApplied = [];
99
+ let tmpOverrideKeys = Object.keys(tmpOverrides);
100
+ for (let j = 0; j < tmpOverrideKeys.length; j++)
101
+ {
102
+ let tmpKey = tmpOverrideKeys[j];
103
+ if (!_RuntimeSyncProperties.hasOwnProperty(tmpKey)) continue;
104
+ let tmpCoerced = _RuntimeSyncProperties[tmpKey](tmpOverrides[tmpKey]);
105
+ if (tmpCoerced === null) continue;
106
+ tmpEntity[tmpKey] = tmpCoerced;
107
+ tmpApplied.push(`${tmpKey}=${tmpCoerced}`);
108
+ }
109
+ if (tmpApplied.length > 0)
110
+ {
111
+ pLog.info(`SyncEntityOptions: ${tmpEntityName} ← ${tmpApplied.join(', ')}`);
112
+ }
113
+ }
114
+ };
115
+
13
116
  module.exports = (pDataClonerService, pOratorServiceServer) =>
14
117
  {
15
118
  let tmpFable = pDataClonerService.fable;
@@ -43,81 +146,9 @@ module.exports = (pDataClonerService, pOratorServiceServer) =>
43
146
  let tmpRequestedMode = tmpBody.SyncMode || 'Initial';
44
147
  let tmpMaxRecords = parseInt(tmpBody.MaxRecordsPerEntity, 10) || 0;
45
148
 
46
- // Update SyncDeletedRecords from request if provided
47
- if (tmpBody.hasOwnProperty('SyncDeletedRecords'))
48
- {
49
- tmpCloneState.SyncDeletedRecords = !!tmpBody.SyncDeletedRecords;
50
- let tmpEntityNames = Object.keys(tmpFable.MeadowSync.MeadowSyncEntities);
51
- for (let i = 0; i < tmpEntityNames.length; i++)
52
- {
53
- tmpFable.MeadowSync.MeadowSyncEntities[tmpEntityNames[i]].SyncDeletedRecords = tmpCloneState.SyncDeletedRecords;
54
- }
55
- }
56
-
57
- // Update MaxRecordsPerEntity on sync entities
58
- if (tmpMaxRecords > 0)
59
- {
60
- let tmpEntityNames = Object.keys(tmpFable.MeadowSync.MeadowSyncEntities);
61
- for (let i = 0; i < tmpEntityNames.length; i++)
62
- {
63
- tmpFable.MeadowSync.MeadowSyncEntities[tmpEntityNames[i]].MaxRecordsPerEntity = tmpMaxRecords;
64
- }
65
- }
66
-
67
- // Update UseAdvancedIDPagination on all sync entities
68
- if (tmpBody.hasOwnProperty('UseAdvancedIDPagination'))
69
- {
70
- let tmpEntityNames = Object.keys(tmpFable.MeadowSync.MeadowSyncEntities);
71
- for (let i = 0; i < tmpEntityNames.length; i++)
72
- {
73
- tmpFable.MeadowSync.MeadowSyncEntities[tmpEntityNames[i]].UseAdvancedIDPagination = !!tmpBody.UseAdvancedIDPagination;
74
- }
75
- }
76
-
77
- // Update DateTimePrecisionMS on MeadowSync and all sync entities
78
- if (tmpBody.hasOwnProperty('DateTimePrecisionMS'))
79
- {
80
- let tmpPrecision = parseInt(tmpBody.DateTimePrecisionMS, 10);
81
- if (!isNaN(tmpPrecision))
82
- {
83
- tmpFable.MeadowSync.DateTimePrecisionMS = tmpPrecision;
84
- let tmpEntityNames = Object.keys(tmpFable.MeadowSync.MeadowSyncEntities);
85
- for (let i = 0; i < tmpEntityNames.length; i++)
86
- {
87
- tmpFable.MeadowSync.MeadowSyncEntities[tmpEntityNames[i]].DateTimePrecisionMS = tmpPrecision;
88
- }
89
- }
90
- }
91
-
92
- // Update BackSyncTimeLimit on MeadowSync and all sync entities
93
- if (tmpBody.hasOwnProperty('BackSyncTimeLimit'))
94
- {
95
- let tmpBackSyncTimeLimit = parseInt(tmpBody.BackSyncTimeLimit, 10);
96
- if (!isNaN(tmpBackSyncTimeLimit) && tmpBackSyncTimeLimit > 0)
97
- {
98
- tmpFable.MeadowSync.BackSyncTimeLimit = tmpBackSyncTimeLimit;
99
- let tmpEntityNames = Object.keys(tmpFable.MeadowSync.MeadowSyncEntities);
100
- for (let i = 0; i < tmpEntityNames.length; i++)
101
- {
102
- tmpFable.MeadowSync.MeadowSyncEntities[tmpEntityNames[i]].BackSyncTimeLimit = tmpBackSyncTimeLimit;
103
- }
104
- }
105
- }
106
-
107
- // Update TrueUpPageSize on MeadowSync and all sync entities
108
- if (tmpBody.hasOwnProperty('TrueUpPageSize'))
109
- {
110
- let tmpTrueUpPageSize = parseInt(tmpBody.TrueUpPageSize, 10);
111
- if (!isNaN(tmpTrueUpPageSize) && tmpTrueUpPageSize > 0)
112
- {
113
- tmpFable.MeadowSync.TrueUpPageSize = tmpTrueUpPageSize;
114
- let tmpEntityNames = Object.keys(tmpFable.MeadowSync.MeadowSyncEntities);
115
- for (let i = 0; i < tmpEntityNames.length; i++)
116
- {
117
- tmpFable.MeadowSync.MeadowSyncEntities[tmpEntityNames[i]].TrueUpPageSize = tmpTrueUpPageSize;
118
- }
119
- }
120
- }
149
+ // Apply global + per-entity runtime sync overrides. See
150
+ // applyRuntimeSyncOverrides() at module scope.
151
+ applyRuntimeSyncOverrides(tmpBody, tmpFable.MeadowSync, tmpCloneState, tmpFable.log);
121
152
 
122
153
  // If no tables specified, sync all entities
123
154
  if (tmpSelectedTables.length === 0)
@@ -579,3 +610,7 @@ module.exports = (pDataClonerService, pOratorServiceServer) =>
579
610
  return fNext();
580
611
  });
581
612
  };
613
+
614
+ // Exposed for unit tests.
615
+ module.exports._RuntimeSyncProperties = _RuntimeSyncProperties;
616
+ module.exports.applyRuntimeSyncOverrides = applyRuntimeSyncOverrides;
@@ -2,35 +2,48 @@ const libPictApplication = require('pict-application');
2
2
 
3
3
  const libProvider = require('./providers/Pict-Provider-DataCloner.js');
4
4
 
5
- const libViewLayout = require('./views/PictView-DataCloner-Layout.js');
5
+ const libViewLayout = require('./views/PictView-DataCloner-Layout.js');
6
6
  const libViewConnection = require('./views/PictView-DataCloner-Connection.js');
7
- const libViewSession = require('./views/PictView-DataCloner-Session.js');
8
- const libViewSchema = require('./views/PictView-DataCloner-Schema.js');
9
- const libViewDeploy = require('./views/PictView-DataCloner-Deploy.js');
10
- const libViewSync = require('./views/PictView-DataCloner-Sync.js');
11
- const libViewExport = require('./views/PictView-DataCloner-Export.js');
12
- const libViewViewData = require('./views/PictView-DataCloner-ViewData.js');
13
- const libViewHistogram = require('pict-section-histogram');
7
+ const libViewSession = require('./views/PictView-DataCloner-Session.js');
8
+ const libViewSchema = require('./views/PictView-DataCloner-Schema.js');
9
+ const libViewDeploy = require('./views/PictView-DataCloner-Deploy.js');
10
+ const libViewSync = require('./views/PictView-DataCloner-Sync.js');
11
+ const libViewExport = require('./views/PictView-DataCloner-Export.js');
12
+ const libViewViewData = require('./views/PictView-DataCloner-ViewData.js');
13
+ const libViewHistogram = require('pict-section-histogram');
14
14
  const libViewConnectionForm = require('pict-section-connection-form');
15
15
 
16
+ const libPictSectionModal = require('pict-section-modal');
17
+ const libPictSectionTheme = require('pict-section-theme');
18
+ const libBrand = require('../../RetoldDataService-Brand.js');
19
+
20
+ const libViewShell = require('./views/PictView-DataCloner-Shell.js');
21
+ const libViewTopBarNav = require('./views/PictView-DataCloner-TopBar-Nav.js');
22
+ const libViewTopBarUser = require('./views/PictView-DataCloner-TopBar-User.js');
23
+ const libViewStatusBar = require('./views/PictView-DataCloner-StatusBar.js');
24
+ const libViewStatusDetail = require('./views/PictView-DataCloner-StatusDetail.js');
25
+ const libViewSettings = require('./views/PictView-DataCloner-SettingsPanel.js');
26
+
16
27
  class DataClonerApplication extends libPictApplication
17
28
  {
18
29
  constructor(pFable, pOptions, pServiceHash)
19
30
  {
20
31
  super(pFable, pOptions, pServiceHash);
21
32
 
22
- // Register provider
23
- this.pict.addProvider('DataCloner', libProvider.default_configuration, libProvider);
33
+ // 1. Modal section (provides shell + panels + modal API).
34
+ this.pict.addView('Pict-Section-Modal',
35
+ libPictSectionModal.default_configuration, libPictSectionModal);
24
36
 
25
- // Register views
26
- this.pict.addView('DataCloner-Layout', libViewLayout.default_configuration, libViewLayout);
37
+ // 2. Provider + existing section views.
38
+ this.pict.addProvider('DataCloner', libProvider.default_configuration, libProvider);
39
+ this.pict.addView('DataCloner-Layout', libViewLayout.default_configuration, libViewLayout);
27
40
  this.pict.addView('DataCloner-Connection', libViewConnection.default_configuration, libViewConnection);
28
- this.pict.addView('DataCloner-Session', libViewSession.default_configuration, libViewSession);
29
- this.pict.addView('DataCloner-Schema', libViewSchema.default_configuration, libViewSchema);
30
- this.pict.addView('DataCloner-Deploy', libViewDeploy.default_configuration, libViewDeploy);
31
- this.pict.addView('DataCloner-Sync', libViewSync.default_configuration, libViewSync);
32
- this.pict.addView('DataCloner-Export', libViewExport.default_configuration, libViewExport);
33
- this.pict.addView('DataCloner-ViewData', libViewViewData.default_configuration, libViewViewData);
41
+ this.pict.addView('DataCloner-Session', libViewSession.default_configuration, libViewSession);
42
+ this.pict.addView('DataCloner-Schema', libViewSchema.default_configuration, libViewSchema);
43
+ this.pict.addView('DataCloner-Deploy', libViewDeploy.default_configuration, libViewDeploy);
44
+ this.pict.addView('DataCloner-Sync', libViewSync.default_configuration, libViewSync);
45
+ this.pict.addView('DataCloner-Export', libViewExport.default_configuration, libViewExport);
46
+ this.pict.addView('DataCloner-ViewData', libViewViewData.default_configuration, libViewViewData);
34
47
  this.pict.addView('DataCloner-StatusHistogram',
35
48
  {
36
49
  ViewIdentifier: 'DataCloner-StatusHistogram',
@@ -43,14 +56,12 @@ class DataClonerApplication extends libPictApplication
43
56
  ShowValues: false,
44
57
  ShowLabels: true,
45
58
  MaxBarSize: 80,
46
- BarColor: '#4a90d9',
59
+ BarColor: 'var(--theme-color-brand-primary, #4a90d9)',
47
60
  Bins: []
48
61
  }, libViewHistogram);
49
62
 
50
- // Shared schema-driven connection form. Renders into the slot
51
- // the DataCloner-Connection accordion shell exposes; the
52
- // provider's bootstrapConnectionSchemas() pumps the schemas in
53
- // once the host's /clone/connection/schemas endpoint responds.
63
+ // Shared schema-driven connection form (renders into the
64
+ // DataCloner-Connection accordion shell's slot).
54
65
  this.pict.addView('PictSection-ConnectionForm',
55
66
  Object.assign({}, libViewConnectionForm.default_configuration,
56
67
  {
@@ -60,21 +71,39 @@ class DataClonerApplication extends libPictApplication
60
71
  ActiveAddress: 'AppData.DataCloner.Connection.ActiveProvider',
61
72
  FieldIDPrefix: 'datacloner-conn'
62
73
  }), libViewConnectionForm);
74
+
75
+ // 3. Shell host + slot views + status bar / detail + settings panel.
76
+ this.pict.addView('DataCloner-Shell',
77
+ libViewShell.default_configuration, libViewShell);
78
+ this.pict.addView('DataCloner-TopBar-Nav',
79
+ libViewTopBarNav.default_configuration, libViewTopBarNav);
80
+ this.pict.addView('DataCloner-TopBar-User',
81
+ libViewTopBarUser.default_configuration, libViewTopBarUser);
82
+ this.pict.addView('DataCloner-StatusBar',
83
+ libViewStatusBar.default_configuration, libViewStatusBar);
84
+ this.pict.addView('DataCloner-StatusDetail',
85
+ libViewStatusDetail.default_configuration, libViewStatusDetail);
86
+ this.pict.addView('DataCloner-SettingsPanel',
87
+ libViewSettings.default_configuration, libViewSettings);
88
+
89
+ // 4. Theme-Section provider — registered LAST so it can find the slot views.
90
+ this.pict.addProvider('Theme-Section',
91
+ {
92
+ ApplyDefault: 'pict-default',
93
+ DefaultMode: 'system',
94
+ DefaultScale: 1.0,
95
+ Brand: libBrand,
96
+ Views: ['Picker', 'ModeToggle', 'ScaleSelect', 'BrandMark', 'TopBar', 'BottomBar'],
97
+ ViewOptions:
98
+ {
99
+ TopBar: { NavView: 'DataCloner-TopBar-Nav', UserView: 'DataCloner-TopBar-User', Height: 56 },
100
+ BottomBar: { StatusView: 'DataCloner-StatusBar', Height: 36 }
101
+ }
102
+ }, libPictSectionTheme);
63
103
  }
64
104
 
65
105
  onAfterInitializeAsync(fCallback)
66
106
  {
67
- // Centralized state (replaces global variables).
68
- //
69
- // PersistFields covers the static, non-connection inputs only.
70
- // Connection-section fields (provider picker + per-provider
71
- // inputs) are schema-driven now: their DOM ids and
72
- // localStorage keys are derived at runtime from the host's
73
- // /clone/connection/schemas response and persistence is hooked
74
- // up by Pict-Provider-DataCloner#bootstrapConnectionSchemas
75
- // after the schema-driven Connection view re-renders. See
76
- // PictView-DataCloner-Connection.js for the field-id
77
- // convention.
78
107
  this.pict.AppData.DataCloner =
79
108
  {
80
109
  FetchedTables: [],
@@ -94,9 +123,6 @@ class DataClonerApplication extends libPictApplication
94
123
  'userName', 'password', 'schemaURL', 'pageSize', 'dateTimePrecisionMS',
95
124
  'syncMaxRecords'
96
125
  ],
97
- // Connection state — populated by bootstrapConnectionSchemas().
98
- // Initialized empty here so the Connection view's first
99
- // onBeforeRender finds a valid (if empty) shape.
100
126
  Connection:
101
127
  {
102
128
  Schemas: [],
@@ -108,15 +134,17 @@ class DataClonerApplication extends libPictApplication
108
134
  }
109
135
  };
110
136
 
111
- // Make pict available for inline onclick handlers
112
137
  window.pict = this.pict;
113
138
 
114
- // Render layout (which chains child view renders via onAfterRender).
115
- // The Connection view renders an empty shell here — the schemas
116
- // arrive asynchronously and trigger a re-render once they land.
139
+ // Render the shell first creates panel destination divs.
140
+ this.pict.views['DataCloner-Shell'].render();
141
+
142
+ // Render the layout (chains child renders) into #DataCloner-Workspace.
117
143
  this.pict.views['DataCloner-Layout'].render();
118
144
 
119
- // Post-render initialization for the static (non-connection) UI.
145
+ // Render the StatusBar into the BottomBar slot.
146
+ this.pict.views['DataCloner-StatusBar'].render();
147
+
120
148
  this.pict.providers.DataCloner.initPersistence();
121
149
  this.pict.providers.DataCloner.restoreDeployedTables();
122
150
  this.pict.providers.DataCloner.startLiveStatusPolling();
@@ -125,11 +153,6 @@ class DataClonerApplication extends libPictApplication
125
153
  this.pict.views['DataCloner-Layout'].collapseAllSections();
126
154
  this.pict.providers.DataCloner.initAutoProcess();
127
155
 
128
- // Async: fetch the host's connection-form schemas and re-render
129
- // the Connection section. bootstrapConnectionSchemas restores
130
- // localStorage values + hooks save listeners once the new DOM
131
- // is in place, then invokes onProviderChange() to surface the
132
- // active provider's form.
133
156
  this.pict.providers.DataCloner.bootstrapConnectionSchemas(function () { /* fire-and-forget */ });
134
157
 
135
158
  return fCallback();
@@ -733,7 +733,7 @@ class DataClonerProvider extends libPictProvider
733
733
  tmpHtml += '<tr' + tmpRowClass + '>';
734
734
  tmpHtml += '<td>' + this.escapeHtml(tmpT.Name) + '</td>';
735
735
  tmpHtml += '<td style="text-align:right; font-variant-numeric:tabular-nums">' + tmpCountFmt + '</td>';
736
- tmpHtml += '<td style="text-align:right; font-variant-numeric:tabular-nums; color:#888">' + tmpTimeFmt + '</td>';
736
+ tmpHtml += '<td style="text-align:right; font-variant-numeric:tabular-nums; color:var(--theme-color-text-muted, #888)">' + tmpTimeFmt + '</td>';
737
737
  tmpHtml += '</tr>';
738
738
  }
739
739
  tmpHtml += '</tbody>';
@@ -908,11 +908,11 @@ class DataClonerProvider extends libPictProvider
908
908
  {
909
909
  if (tmpIsLive)
910
910
  {
911
- tmpHtml = '<div style="font-size:0.9em; color:#888; padding:8px 0">Sync in progress, waiting for table data\u2026</div>';
911
+ tmpHtml = '<div style="font-size:0.9em; color:var(--theme-color-text-muted, #888); padding:8px 0">Sync in progress, waiting for table data\u2026</div>';
912
912
  }
913
913
  else
914
914
  {
915
- tmpHtml = '<div style="font-size:0.9em; color:#888; padding:8px 0">No sync data available. Run a sync to see operation details here.</div>';
915
+ tmpHtml = '<div style="font-size:0.9em; color:var(--theme-color-text-muted, #888); padding:8px 0">No sync data available. Run a sync to see operation details here.</div>';
916
916
  }
917
917
  }
918
918
 
@@ -51,7 +51,7 @@ const _ViewConfiguration =
51
51
  <div class="accordion-toggle">&#9660;</div>
52
52
  </div>
53
53
  <div class="accordion-body">
54
- <p style="font-size:0.9em; color:#666; margin-bottom:10px">Configure the local database where cloned data will be stored. The provider list comes from the host's installed meadow-connection modules.</p>
54
+ <p style="font-size:0.9em; color:var(--theme-color-text-secondary, #666); margin-bottom:10px">Configure the local database where cloned data will be stored. The provider list comes from the host's installed meadow-connection modules.</p>
55
55
 
56
56
  <div class="inline-group" style="margin-bottom:10px">
57
57
  <div style="flex:1; display:flex; align-items:flex-end; gap:8px; justify-content:flex-end">
@@ -62,7 +62,7 @@ class DataClonerDeployView extends libPictView
62
62
  auditGUIDIndices()
63
63
  {
64
64
  let tmpReportEl = document.getElementById('guidIndexReport');
65
- if (tmpReportEl) tmpReportEl.innerHTML = '<span style="color:#888">Checking GUID indices...</span>';
65
+ if (tmpReportEl) tmpReportEl.innerHTML = '<span style="color:var(--theme-color-text-muted, #888)">Checking GUID indices...</span>';
66
66
 
67
67
  let tmpSelf = this;
68
68
  this.pict.providers.DataCloner.api('GET', '/clone/schema/guid-index-audit')
@@ -84,7 +84,7 @@ class DataClonerDeployView extends libPictView
84
84
 
85
85
  let tmpHTML = '<div style="margin-top:6px"><strong>' + pData.Message + '</strong></div>';
86
86
  tmpHTML += '<table style="font-size:0.85em; margin:6px 0; border-collapse:collapse; width:100%">';
87
- tmpHTML += '<tr style="text-align:left; border-bottom:1px solid #ccc"><th style="padding:3px 8px">Table</th><th style="padding:3px 8px">GUID Column</th><th style="padding:3px 8px">Index</th></tr>';
87
+ tmpHTML += '<tr style="text-align:left; border-bottom:1px solid var(--theme-color-border-default, #ccc)"><th style="padding:3px 8px">Table</th><th style="padding:3px 8px">GUID Column</th><th style="padding:3px 8px">Index</th></tr>';
88
88
 
89
89
  for (let t = 0; t < pData.Tables.length; t++)
90
90
  {
@@ -95,7 +95,7 @@ class DataClonerDeployView extends libPictView
95
95
  let tmpStatus = tmpCol.HasIndex
96
96
  ? '<span style="color:green">' + tmpCol.IndexName + '</span>'
97
97
  : '<span style="color:red">MISSING</span>';
98
- tmpHTML += '<tr style="border-bottom:1px solid #eee"><td style="padding:3px 8px">' + tmpTable.Table + '</td><td style="padding:3px 8px">' + tmpCol.Column + '</td><td style="padding:3px 8px">' + tmpStatus + '</td></tr>';
98
+ tmpHTML += '<tr style="border-bottom:1px solid var(--theme-color-border-light, #eee)"><td style="padding:3px 8px">' + tmpTable.Table + '</td><td style="padding:3px 8px">' + tmpCol.Column + '</td><td style="padding:3px 8px">' + tmpStatus + '</td></tr>';
99
99
  }
100
100
  }
101
101
  tmpHTML += '</table>';
@@ -112,7 +112,7 @@ class DataClonerDeployView extends libPictView
112
112
  createMissingGUIDIndices()
113
113
  {
114
114
  let tmpReportEl = document.getElementById('guidIndexReport');
115
- if (tmpReportEl) tmpReportEl.innerHTML = '<span style="color:#888">Creating GUID indices...</span>';
115
+ if (tmpReportEl) tmpReportEl.innerHTML = '<span style="color:var(--theme-color-text-muted, #888)">Creating GUID indices...</span>';
116
116
 
117
117
  let tmpSelf = this;
118
118
  this.pict.providers.DataCloner.api('POST', '/clone/schema/guid-index-create')
@@ -206,7 +206,7 @@ module.exports.default_configuration =
206
206
  <div class="accordion-toggle">&#9660;</div>
207
207
  </div>
208
208
  <div class="accordion-body">
209
- <p style="font-size:0.9em; color:#666; margin-bottom:10px">Creates the selected tables in the local database and sets up CRUD endpoints (e.g. GET /1.0/Documents).</p>
209
+ <p style="font-size:0.9em; color:var(--theme-color-text-secondary, #666); margin-bottom:10px">Creates the selected tables in the local database and sets up CRUD endpoints (e.g. GET /1.0/Documents).</p>
210
210
  <button class="primary" onclick="pict.views['DataCloner-Deploy'].deploySchema()">Deploy Selected Tables</button>
211
211
  <button onclick="pict.views['DataCloner-Deploy'].auditGUIDIndices()">Check GUID Indices</button>
212
212
  <button class="danger" onclick="pict.views['DataCloner-Deploy'].resetDatabase()">Reset Database</button>
@@ -450,7 +450,7 @@ module.exports.default_configuration =
450
450
  <div class="accordion-toggle">&#9660;</div>
451
451
  </div>
452
452
  <div class="accordion-body">
453
- <p style="font-size:0.9em; color:#666; margin-bottom:10px">Generate a JSON config file from your current settings. Use it to run headless clones from the command line.</p>
453
+ <p style="font-size:0.9em; color:var(--theme-color-text-secondary, #666); margin-bottom:10px">Generate a JSON config file from your current settings. Use it to run headless clones from the command line.</p>
454
454
  <div style="display:flex; gap:8px; margin-bottom:10px">
455
455
  <button class="primary" onclick="pict.views['DataCloner-Export'].generateConfig()">Generate Config</button>
456
456
  <button class="secondary" onclick="pict.views['DataCloner-Export'].copyConfig()">Copy to Clipboard</button>
@@ -458,28 +458,28 @@ module.exports.default_configuration =
458
458
  </div>
459
459
  <div id="configExportStatus"></div>
460
460
  <div id="cliCommand" style="display:none; margin-bottom:10px">
461
- <label style="margin-bottom:4px">CLI Command <span style="color:#888; font-weight:normal">(with config file)</span></label>
462
- <div style="background:#1a1a1a; color:#4fc3f7; padding:10px 14px; border-radius:4px; font-family:monospace; font-size:0.9em; word-break:break-all; cursor:pointer" onclick="pict.views['DataCloner-Export'].copyCLI()" title="Click to copy"></div>
461
+ <label style="margin-bottom:4px">CLI Command <span style="color:var(--theme-color-text-muted, #888); font-weight:normal">(with config file)</span></label>
462
+ <div style="background:var(--theme-color-text-primary, #1a1a1a); color:var(--theme-color-status-info, #4fc3f7); padding:10px 14px; border-radius:4px; font-family:monospace; font-size:0.9em; word-break:break-all; cursor:pointer" onclick="pict.views['DataCloner-Export'].copyCLI()" title="Click to copy"></div>
463
463
  </div>
464
464
  <div id="cliOneShot" style="display:none; margin-bottom:10px">
465
- <label style="margin-bottom:4px">One-liner <span style="color:#888; font-weight:normal">(no config file needed)</span></label>
466
- <div style="background:#1a1a1a; color:#4fc3f7; padding:10px 14px; border-radius:4px; font-family:monospace; font-size:0.9em; word-break:break-all; cursor:pointer; white-space:pre-wrap" onclick="pict.views['DataCloner-Export'].copyOneShot()" title="Click to copy"></div>
465
+ <label style="margin-bottom:4px">One-liner <span style="color:var(--theme-color-text-muted, #888); font-weight:normal">(no config file needed)</span></label>
466
+ <div style="background:var(--theme-color-text-primary, #1a1a1a); color:var(--theme-color-status-info, #4fc3f7); padding:10px 14px; border-radius:4px; font-family:monospace; font-size:0.9em; word-break:break-all; cursor:pointer; white-space:pre-wrap" onclick="pict.views['DataCloner-Export'].copyOneShot()" title="Click to copy"></div>
467
467
  </div>
468
- <textarea id="configOutput" style="display:none; width:100%; min-height:300px; font-family:monospace; font-size:0.85em; padding:10px; border:1px solid #ccc; border-radius:4px; background:#fafafa; tab-size:4; resize:vertical" readonly></textarea>
468
+ <textarea id="configOutput" style="display:none; width:100%; min-height:300px; font-family:monospace; font-size:0.85em; padding:10px; border:1px solid var(--theme-color-border-default, #ccc); border-radius:4px; background:var(--theme-color-background-secondary, #fafafa); tab-size:4; resize:vertical" readonly></textarea>
469
469
 
470
- <div id="mdwintExport" style="display:none; margin-top:16px; padding-top:16px; border-top:1px solid #eee">
471
- <h3 style="margin:0 0 8px; font-size:1em">meadow-integration CLI <span style="color:#888; font-weight:normal; font-size:0.85em">(mdwint clone)</span></h3>
472
- <p style="font-size:0.85em; color:#666; margin-bottom:8px">Save as <code>.meadow.config.json</code> in your project root, then run the command below. Requires a local Meadow extended schema JSON file.</p>
470
+ <div id="mdwintExport" style="display:none; margin-top:16px; padding-top:16px; border-top:1px solid var(--theme-color-border-light, #eee)">
471
+ <h3 style="margin:0 0 8px; font-size:1em">meadow-integration CLI <span style="color:var(--theme-color-text-muted, #888); font-weight:normal; font-size:0.85em">(mdwint clone)</span></h3>
472
+ <p style="font-size:0.85em; color:var(--theme-color-text-secondary, #666); margin-bottom:8px">Save as <code>.meadow.config.json</code> in your project root, then run the command below. Requires a local Meadow extended schema JSON file.</p>
473
473
  <div style="display:flex; gap:8px; margin-bottom:10px">
474
474
  <button class="secondary" onclick="pict.views['DataCloner-Export'].copyMdwintConfig()">Copy Config</button>
475
475
  <button class="secondary" onclick="pict.views['DataCloner-Export'].downloadMdwintConfig()">Download .meadow.config.json</button>
476
476
  </div>
477
477
  <div id="mdwintCLICommand" style="margin-bottom:10px">
478
478
  <label style="margin-bottom:4px">CLI Command</label>
479
- <div style="background:#1a1a1a; color:#4fc3f7; padding:10px 14px; border-radius:4px; font-family:monospace; font-size:0.9em; word-break:break-all; cursor:pointer" onclick="pict.views['DataCloner-Export'].copyMdwintCLI()" title="Click to copy"></div>
479
+ <div style="background:var(--theme-color-text-primary, #1a1a1a); color:var(--theme-color-status-info, #4fc3f7); padding:10px 14px; border-radius:4px; font-family:monospace; font-size:0.9em; word-break:break-all; cursor:pointer" onclick="pict.views['DataCloner-Export'].copyMdwintCLI()" title="Click to copy"></div>
480
480
  </div>
481
481
  <div id="mdwintConfigStatus"></div>
482
- <textarea id="mdwintConfigOutput" style="width:100%; min-height:250px; font-family:monospace; font-size:0.85em; padding:10px; border:1px solid #ccc; border-radius:4px; background:#fafafa; tab-size:4; resize:vertical" readonly></textarea>
482
+ <textarea id="mdwintConfigOutput" style="width:100%; min-height:250px; font-family:monospace; font-size:0.85em; padding:10px; border:1px solid var(--theme-color-border-default, #ccc); border-radius:4px; background:var(--theme-color-background-secondary, #fafafa); tab-size:4; resize:vertical" readonly></textarea>
483
483
  </div>
484
484
  </div>
485
485
  </div>