@voxgig/build 4.3.0 → 4.11.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.
Files changed (100) hide show
  1. package/README.md +51 -2
  2. package/build.ts +20 -0
  3. package/dist/api/api_gen.d.ts +7 -0
  4. package/dist/api/api_gen.js +370 -0
  5. package/dist/api/api_gen.js.map +1 -0
  6. package/dist/build.d.ts +16 -1
  7. package/dist/build.js +17 -1
  8. package/dist/build.js.map +1 -1
  9. package/dist/doc/doc_gen.d.ts +8 -0
  10. package/dist/doc/doc_gen.js +397 -0
  11. package/dist/doc/doc_gen.js.map +1 -0
  12. package/dist/env/web/web_gen.js +159 -1
  13. package/dist/env/web/web_gen.js.map +1 -1
  14. package/dist/shape/ent.js +4 -2
  15. package/dist/shape/ent.js.map +1 -1
  16. package/env/web/web_gen.ts +174 -1
  17. package/package.json +6 -9
  18. package/tm/web/backend/env/shared/seed.ts.frag +23 -0
  19. package/tm/web/backend/env/web/api.ts.frag +157 -0
  20. package/tm/web/backend/env/web/web.ts.frag +37 -4
  21. package/tm/web/backend/srv/api/api-srv.ts.frag +4 -0
  22. package/tm/web/backend/srv/api/expose.ts.frag +33 -0
  23. package/tm/web/backend/srv/api/get_info.ts.frag +6 -0
  24. package/tm/web/backend/srv/api/on_ent.ts.frag +124 -0
  25. package/tm/web/backend/srv/auth/apikey_util.ts.frag +10 -0
  26. package/tm/web/backend/srv/auth/auth-srv.ts.frag +1 -0
  27. package/tm/web/backend/srv/auth/change_pass.ts.frag +12 -0
  28. package/tm/web/backend/srv/auth/create_apikey.ts.frag +33 -0
  29. package/tm/web/backend/srv/auth/get_info.ts.frag +1 -0
  30. package/tm/web/backend/srv/auth/list_apikey.ts.frag +15 -0
  31. package/tm/web/backend/srv/auth/load_auth.ts.frag +10 -1
  32. package/tm/web/backend/srv/auth/remind_pass.ts.frag +35 -0
  33. package/tm/web/backend/srv/auth/revoke_apikey.ts.frag +23 -0
  34. package/tm/web/backend/srv/auth/signin_user.ts.frag +12 -2
  35. package/tm/web/backend/srv/auth/signout_user.ts.frag +11 -1
  36. package/tm/web/backend/srv/auth/update_user.ts.frag +15 -0
  37. package/tm/web/backend/srv/auth/user_util.ts.frag +21 -0
  38. package/tm/web/backend/srv/auth/web_change_pass.ts.frag +16 -0
  39. package/tm/web/backend/srv/auth/web_create_apikey.ts.frag +15 -0
  40. package/tm/web/backend/srv/auth/web_list_apikey.ts.frag +11 -0
  41. package/tm/web/backend/srv/auth/web_load_auth.ts.frag +1 -0
  42. package/tm/web/backend/srv/auth/web_remind_pass.ts.frag +7 -0
  43. package/tm/web/backend/srv/auth/web_revoke_apikey.ts.frag +14 -0
  44. package/tm/web/backend/srv/auth/web_signin_user.ts.frag +1 -0
  45. package/tm/web/backend/srv/auth/web_signout_user.ts.frag +11 -3
  46. package/tm/web/backend/srv/auth/web_update_user.ts.frag +11 -0
  47. package/tm/web/backend/srv/ent/access.ts.frag +179 -0
  48. package/tm/web/backend/srv/ent/cmd_list.ts.frag +86 -0
  49. package/tm/web/backend/srv/ent/cmd_load.ts.frag +56 -0
  50. package/tm/web/backend/srv/ent/cmd_remove.ts.frag +60 -0
  51. package/tm/web/backend/srv/ent/cmd_save.ts.frag +141 -0
  52. package/tm/web/backend/srv/ent/ent-srv.ts.frag +4 -0
  53. package/tm/web/backend/srv/ent/get_info.ts.frag +6 -0
  54. package/tm/web/backend/srv/ent/web_cmd_list.ts.frag +17 -0
  55. package/tm/web/backend/srv/ent/web_cmd_load.ts.frag +17 -0
  56. package/tm/web/backend/srv/ent/web_cmd_remove.ts.frag +17 -0
  57. package/tm/web/backend/srv/ent/web_cmd_save.ts.frag +17 -0
  58. package/tm/web/backend/test/unit/env/web/api-router.test.ts.frag +315 -0
  59. package/tm/web/backend/test/unit/env/web/surface.test.ts.frag +109 -0
  60. package/tm/web/backend/test/unit/srv/api/api.setup.ts.frag +162 -0
  61. package/tm/web/backend/test/unit/srv/api/api.test.ts.frag +225 -0
  62. package/tm/web/backend/test/unit/srv/auth/apikey.test.ts.frag +152 -0
  63. package/tm/web/backend/test/unit/srv/auth/auth.setup.ts.frag +40 -0
  64. package/tm/web/backend/test/unit/srv/auth/session.test.ts.frag +189 -0
  65. package/tm/web/backend/test/unit/srv/ent/ent.setup.ts.frag +53 -0
  66. package/tm/web/backend/test/unit/srv/ent/proxy.test.ts.frag +109 -0
  67. package/tm/web/docs/explanation/web-architecture.md.frag +50 -0
  68. package/tm/web/docs/how-to/add-a-custom-entity-view.md.frag +53 -0
  69. package/tm/web/docs/how-to/change-the-theme.md.frag +63 -0
  70. package/tm/web/docs/how-to/customise-the-web-app.md.frag +68 -0
  71. package/tm/web/docs/how-to/use-the-api.md.frag +53 -0
  72. package/tm/web/docs/reference/web-app.md.frag +98 -0
  73. package/tm/web/web/AGENTS.md.frag +52 -0
  74. package/tm/web/web/e2e/smoke.spec.js.frag +13 -11
  75. package/tm/web/web/index.html.frag +3 -0
  76. package/tm/web/web/package.json.frag +4 -1
  77. package/tm/web/web/playwright.config.js.frag +6 -1
  78. package/tm/web/web/src/api.js.frag +92 -0
  79. package/tm/web/web/src/bus.js.frag +46 -23
  80. package/tm/web/web/src/cmp/admin.js.frag +292 -193
  81. package/tm/web/web/src/cmp/admin.md.frag +51 -0
  82. package/tm/web/web/src/cmp/app.js.frag +12 -15
  83. package/tm/web/web/src/cmp/app.md.frag +28 -0
  84. package/tm/web/web/src/cmp/auth.js.frag +69 -41
  85. package/tm/web/web/src/cmp/auth.md.frag +36 -0
  86. package/tm/web/web/src/cmp/public.js.frag +58 -0
  87. package/tm/web/web/src/cmp/public.md.frag +26 -0
  88. package/tm/web/web/src/cmp/settings.js.frag +178 -0
  89. package/tm/web/web/src/cmp/settings.md.frag +30 -0
  90. package/tm/web/web/src/cmp/shell.js.frag +231 -0
  91. package/tm/web/web/src/cmp/shell.md.frag +49 -0
  92. package/tm/web/web/src/cmp/view/custom-view.js.frag +47 -0
  93. package/tm/web/web/src/cmp/view/custom-view.md.frag +31 -0
  94. package/tm/web/web/src/custom.css.frag +8 -0
  95. package/tm/web/web/src/customise.js.frag +30 -0
  96. package/tm/web/web/src/hooks.js.frag +79 -0
  97. package/tm/web/web/src/main.js.frag +17 -4
  98. package/tm/web/web/src/model.js.frag +162 -0
  99. package/tm/web/web/src/style.css.frag +135 -21
  100. package/tm/web/web/src/theme.js.frag +82 -0
@@ -1,26 +1,140 @@
1
+ /* Design tokens (--vg-*) come from the model theme via theme.css; each mode
2
+ sets them under :root[data-theme-mode]. Override in custom.css. */
1
3
  * { box-sizing: border-box; }
2
- body { font: 15px/1.5 system-ui, sans-serif; margin: 0; color: #1c2733; }
3
- header { display: flex; align-items: center; justify-content: space-between;
4
- padding: 0.6rem 1.2rem; border-bottom: 2px solid #e3e8ee; }
5
- header h1 { font-size: 1.2rem; margin: 0; }
6
- main { padding: 1.2rem; max-width: 60rem; }
7
-
8
- .vg-auth-bar { display: flex; gap: 0.8rem; align-items: center; }
9
- .vg-auth-form { max-width: 22rem; display: grid; gap: 0.6rem; }
10
- .vg-auth-form label { display: grid; gap: 0.2rem; }
11
- .vg-auth-err { color: #b3261e; min-height: 1.2em; }
4
+ body { font: var(--vg-font); margin: 0; color: var(--vg-text); background: var(--vg-bg); }
5
+ button { cursor: pointer; }
6
+ a { color: var(--vg-primary); }
7
+ .vg-muted { color: var(--vg-muted); }
8
+ .vg-hint { color: var(--vg-muted); }
9
+ .vg-link { background: none; border: none; color: #1f6feb; cursor: pointer; padding: 0; font: inherit; text-decoration: none; }
10
+ .vg-link:hover { text-decoration: underline; }
11
+ .vg-primary { background: var(--vg-primary); color: #fff; border: none; border-radius: 6px; padding: 0.4rem 0.9rem; }
12
+ .vg-primary:hover { background: var(--vg-primary-dark); }
13
+
14
+ /* ---- enterprise shell ---- */
15
+ .vg-shell { display: flex; flex-direction: column; height: 100vh; }
16
+ .vg-topbar { display: flex; align-items: center; gap: 0.9rem; padding: 0 1rem; height: 52px;
17
+ background: var(--vg-topbar-bg); color: var(--vg-topbar-fg); flex: 0 0 auto; }
18
+ .vg-topbar .vg-brand { font-weight: 700; font-size: 1.05rem; white-space: nowrap; }
19
+ .vg-icon-btn { background: none; border: none; color: #e6edf3; font-size: 1.2rem; }
20
+ .vg-spacer { flex: 1; }
21
+ .vg-project-picker { display: flex; align-items: center; gap: 0.4rem; color: #aeb9c5; }
22
+ .vg-project-picker select { background: #1b2a3a; color: #e6edf3; border: 1px solid #2b3d4f;
23
+ border-radius: 6px; padding: 0.25rem 0.5rem; }
24
+ .vg-usermenu { position: relative; }
25
+ .vg-user-btn { background: #1b2a3a; color: #e6edf3; border: 1px solid #2b3d4f; border-radius: 6px;
26
+ padding: 0.3rem 0.7rem; display: flex; gap: 0.4rem; align-items: center; }
27
+ .vg-user-dropdown { position: absolute; right: 0; top: 110%; background: var(--vg-surface); color: var(--vg-text);
28
+ border: 1px solid var(--vg-border); border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.15);
29
+ min-width: 190px; display: flex; flex-direction: column; z-index: 20; }
30
+ .vg-user-dropdown[hidden] { display: none; }
31
+ .vg-user-dropdown a { padding: 0.6rem 0.9rem; text-decoration: none; color: var(--vg-text); }
32
+ .vg-user-dropdown a:hover { background: var(--vg-bg); }
33
+
34
+ .vg-body { display: flex; flex: 1; min-height: 0; }
35
+ .vg-sidebar { flex: 0 0 220px; background: var(--vg-surface); border-right: 1px solid var(--vg-border); overflow-y: auto;
36
+ padding: 0.7rem; }
37
+ .vg-collapsed .vg-sidebar { display: none; }
38
+ .vg-ent-filter { width: 100%; padding: 0.35rem 0.55rem; border: 1px solid var(--vg-border); border-radius: 6px;
39
+ margin-bottom: 0.6rem; }
40
+ .vg-navgroup { margin-bottom: 0.8rem; }
41
+ .vg-navgroup-title { font-size: 0.72rem; text-transform: uppercase; letter-spacing: .05em;
42
+ color: #8b949e; padding: 0.2rem 0.4rem; }
43
+ .vg-navlink { display: block; padding: 0.35rem 0.6rem; border-radius: 6px; text-decoration: none;
44
+ color: var(--vg-text); }
45
+ .vg-navlink:hover { background: var(--vg-bg); }
46
+ .vg-navlink.vg-sel { background: var(--vg-accent-bg); color: var(--vg-primary); font-weight: 600; }
47
+
48
+ .vg-main { flex: 1; overflow-y: auto; padding: 1.4rem; }
49
+ .vg-entity { max-width: 60rem; }
50
+ .vg-entity-head { display: flex; align-items: center; gap: 0.9rem; margin-bottom: 1rem; }
51
+ .vg-entity-head h2, .vg-entity-head h3 { margin: 0; flex: 1; }
52
+ .vg-empty { color: var(--vg-muted); padding: 2rem; text-align: center; }
53
+
54
+ /* ---- tables ---- */
55
+ .vg-table { border-collapse: collapse; width: 100%; background: var(--vg-surface); border: 1px solid var(--vg-border);
56
+ border-radius: 8px; overflow: hidden; }
57
+ .vg-table th, .vg-table td { text-align: left; padding: 0.5rem 0.7rem; border-bottom: 1px solid var(--vg-border); }
58
+ .vg-table th { background: var(--vg-bg); font-size: 0.8rem; text-transform: uppercase; letter-spacing: .03em;
59
+ color: var(--vg-muted); }
60
+ .vg-actions { text-align: right; white-space: nowrap; }
61
+ .vg-actions button { margin-left: 0.3rem; padding: 0.2rem 0.55rem; border: 1px solid var(--vg-border);
62
+ background: var(--vg-surface); border-radius: 5px; }
63
+ .vg-actions button:hover { background: var(--vg-bg); }
64
+ .vg-ref { text-decoration: none; }
65
+ .vg-ref:hover { text-decoration: underline; }
12
66
 
13
- .vg-admin nav { margin-bottom: 1rem; }
14
- .vg-admin nav a { margin-right: 0.8rem; text-decoration: none; }
15
- .vg-admin nav a.sel { font-weight: 700; border-bottom: 2px solid #1c2733; }
67
+ /* ---- detail ---- */
68
+ .vg-detail { border-collapse: collapse; width: 100%; max-width: 40rem; margin-bottom: 1.6rem;
69
+ background: var(--vg-surface); border: 1px solid var(--vg-border); border-radius: 8px; overflow: hidden; }
70
+ .vg-detail th { text-align: left; width: 30%; padding: 0.5rem 0.7rem; color: var(--vg-muted);
71
+ background: var(--vg-bg); border-bottom: 1px solid var(--vg-border); }
72
+ .vg-detail td { padding: 0.5rem 0.7rem; border-bottom: 1px solid var(--vg-border); }
73
+ .vg-children { margin: 1.4rem 0; }
16
74
 
17
- .vg-table { border-collapse: collapse; width: 100%; }
18
- .vg-table th, .vg-table td { text-align: left; padding: 0.35rem 0.6rem;
19
- border-bottom: 1px solid #e3e8ee; }
75
+ /* ---- forms ---- */
76
+ .vg-entity-form { display: grid; gap: 0.7rem; max-width: 28rem; background: var(--vg-surface); padding: 1.2rem;
77
+ border: 1px solid var(--vg-border); border-radius: 8px; }
78
+ .vg-entity-form label { display: grid; gap: 0.25rem; font-size: 0.9rem; color: var(--vg-text); }
79
+ .vg-entity-form input, .vg-entity-form select { padding: 0.4rem 0.55rem; border: 1px solid var(--vg-border);
80
+ border-radius: 6px; font: inherit; }
81
+ .vg-entity-form input[type=checkbox] { justify-self: start; }
82
+ .vg-form-actions { display: flex; gap: 0.8rem; align-items: center; margin-top: 0.4rem; }
83
+ .vg-form-err { color: #b3261e; min-height: 1.2em; }
84
+ .vg-form-msg { min-height: 1.2em; }
85
+ .vg-form-msg.vg-ok { color: #1a7f37; }
86
+ .vg-form-msg.vg-err { color: #b3261e; }
87
+ .vg-settings .vg-card { background: var(--vg-surface); border: 1px solid var(--vg-border); border-radius: 8px;
88
+ padding: 1.2rem; margin-bottom: 1.2rem; max-width: 32rem; }
89
+ .vg-settings .vg-card h3 { margin-top: 0; }
90
+
91
+ /* ---- custom views (card grid) ---- */
92
+ .vg-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 1rem; }
93
+ .vg-card { background: var(--vg-surface); border: 1px solid var(--vg-border); border-radius: 10px; padding: 1.1rem; }
94
+ .vg-card h3 { margin: 0 0 0.3rem; }
95
+ .vg-card > p { margin: 0.2rem 0; }
96
+ .vg-proj-card { display: flex; flex-direction: column; gap: 0.4rem; }
97
+ .vg-proj-stats { display: flex; justify-content: space-between; color: var(--vg-muted); font-size: 0.85rem; }
98
+ .vg-proj-bar { height: 6px; background: var(--vg-border); border-radius: 4px; overflow: hidden; }
99
+ .vg-proj-bar > div { height: 100%; background: #1f6feb; }
100
+ .vg-proj-card .vg-open { align-self: flex-start; margin-top: 0.3rem; border: 1px solid var(--vg-border);
101
+ background: var(--vg-surface); border-radius: 5px; padding: 0.25rem 0.7rem; }
102
+
103
+ /* ---- public site ---- */
104
+ .vg-public-nav { display: flex; align-items: center; gap: 1.5rem; padding: 0.9rem 2rem;
105
+ background: #0d1b2a; color: #e6edf3; }
106
+ .vg-public-nav .vg-brand { font-weight: 700; font-size: 1.1rem; }
107
+ .vg-public-nav nav { margin-left: auto; display: flex; gap: 1.2rem; }
108
+ .vg-public-nav a { color: #aeb9c5; text-decoration: none; }
109
+ .vg-hero { display: flex; flex-wrap: wrap; gap: 2rem; padding: 3.5rem 2rem; max-width: 68rem;
110
+ margin: 0 auto; align-items: center; }
111
+ .vg-hero-copy { flex: 1 1 340px; }
112
+ .vg-hero-copy h1 { font-size: 2.4rem; margin: 0 0 0.8rem; }
113
+ .vg-hero-copy p { font-size: 1.15rem; color: #44515f; }
114
+ .vg-hero-auth { flex: 0 0 320px; }
115
+ .vg-features, .vg-about { max-width: 68rem; margin: 0 auto; padding: 2.5rem 2rem; }
116
+ .vg-feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.2rem; }
117
+ .vg-feature { background: var(--vg-surface); border: 1px solid var(--vg-border); border-radius: 10px; padding: 1.2rem; }
118
+ .vg-feature h3 { margin-top: 0; }
119
+ .vg-public-footer { padding: 2rem; text-align: center; color: #8b949e; border-top: 1px solid var(--vg-border); }
120
+
121
+ /* ---- auth form ---- */
122
+ .vg-auth-form { display: grid; gap: 0.6rem; background: var(--vg-surface); padding: 1.4rem; border-radius: 10px;
123
+ border: 1px solid var(--vg-border); box-shadow: 0 6px 24px rgba(13,27,42,.08); }
124
+ .vg-auth-form h2 { margin: 0 0 0.3rem; }
125
+ .vg-auth-form label { display: grid; gap: 0.2rem; }
126
+ .vg-auth-form input { padding: 0.45rem 0.55rem; border: 1px solid var(--vg-border); border-radius: 6px; font: inherit; }
127
+ .vg-auth-form button[type=submit] { background: #1f6feb; color: #fff; border: none; border-radius: 6px;
128
+ padding: 0.5rem; font-size: 1rem; }
129
+ .vg-auth-err { color: #b3261e; min-height: 1.2em; }
130
+ .vg-auth-msg { color: #1a7f37; min-height: 1.2em; }
20
131
 
21
- .vg-entity-form { margin-top: 1.2rem; max-width: 26rem; display: grid;
22
- gap: 0.6rem; padding: 1rem; border: 1px solid #e3e8ee; border-radius: 6px; }
23
- .vg-entity-form label { display: grid; gap: 0.2rem; }
132
+ /* API keys (Settings & security) */
133
+ .vg-apikey-once { background: var(--vg-accent-bg); border: 1px solid var(--vg-border);
134
+ border-radius: var(--vg-radius); padding: 10px 12px; margin: 10px 0; }
135
+ .vg-apikey-value { display: block; margin-top: 6px; word-break: break-all; user-select: all; }
136
+ .vg-apikey-table { margin-top: 10px; }
137
+ .vg-apikey-revoke { background: none; border: 1px solid var(--vg-border); border-radius: var(--vg-radius);
138
+ color: var(--vg-text); padding: 2px 10px; cursor: pointer; }
139
+ .vg-apikey-revoke:hover { border-color: #b3261e; color: #b3261e; }
24
140
 
25
- button { cursor: pointer; padding: 0.25rem 0.7rem; }
26
- .vg-hint { color: #5b6b7b; }
@@ -0,0 +1,82 @@
1
+ // Theme controller. The design theme and its named modes come from the model
2
+ // (main.theme); the generated theme.css defines each mode's CSS variables.
3
+ // This switches the active mode via the `data-theme-mode` attribute on <html>,
4
+ // persisting the choice in localStorage. Modes can be customised with the
5
+ // `theme:modes` hook.
6
+
7
+ import { loadModel } from './model.js'
8
+ import * as Hooks from './hooks.js'
9
+
10
+ const KEY = 'vg-theme-mode'
11
+ let _default = 'light'
12
+ let _modes = ['light']
13
+
14
+ function saved() {
15
+ try {
16
+ return localStorage.getItem(KEY)
17
+ }
18
+ catch (e) {
19
+ return null
20
+ }
21
+ }
22
+
23
+ function apply(mode) {
24
+ if (_modes.indexOf(mode) < 0) {
25
+ mode = _default
26
+ }
27
+ document.documentElement.setAttribute('data-theme-mode', mode)
28
+ try {
29
+ localStorage.setItem(KEY, mode)
30
+ }
31
+ catch (e) {
32
+ // ignore storage failures
33
+ }
34
+ }
35
+
36
+ // The available modes, filterable by the theme:modes hook.
37
+ function modes() {
38
+ return Hooks.filter('theme:modes', _modes.slice(), {})
39
+ }
40
+
41
+ function current() {
42
+ return document.documentElement.getAttribute('data-theme-mode') || _default
43
+ }
44
+
45
+ function setMode(mode) {
46
+ apply(mode)
47
+ }
48
+
49
+ // Cycle to the next mode (used by the shell's mode toggle).
50
+ function nextMode() {
51
+ const ms = modes()
52
+ const i = ms.indexOf(current())
53
+ setMode(ms[(i + 1) % ms.length])
54
+ return current()
55
+ }
56
+
57
+ async function init() {
58
+ const m = await loadModel()
59
+ const theme = (m && m.main && m.main.theme) || {}
60
+ _modes = Object.keys(theme.modes || { light: {} })
61
+ _default = theme.mode || _modes[0] || 'light'
62
+ apply(saved() || _default)
63
+ }
64
+
65
+ // Apply the persisted mode synchronously on import (before the model loads)
66
+ // to avoid a flash of the default theme; init() then refines the mode list.
67
+ if ('undefined' !== typeof document) {
68
+ const s = saved()
69
+ if (s) {
70
+ document.documentElement.setAttribute('data-theme-mode', s)
71
+ }
72
+ init()
73
+ }
74
+
75
+ export {
76
+ init,
77
+ modes,
78
+ current,
79
+ setMode,
80
+ nextMode,
81
+ }
82
+