@genesislcap/blank-app-seed 5.27.2-prerelease.3 → 5.28.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 (36) hide show
  1. package/.genx/ai-consumer.json +18 -0
  2. package/.genx/configure.js +122 -0
  3. package/.genx/package.json +1 -1
  4. package/.genx/scripts/check-ai-declaration.mjs +116 -0
  5. package/.genx/scripts/check-ai-emission.sh +974 -0
  6. package/.genx/scripts/generate-test-apps.sh +38 -4
  7. package/.genx/templates/react/ai/assistant-host.ts.hbs +30 -0
  8. package/.genx/templates/react/ai/assistant.ts.hbs +52 -0
  9. package/.genx/templates/react/ai/extensions.ts.hbs +17 -0
  10. package/.genx/templates/react/ai/pbc-elements.ts.hbs +17 -0
  11. package/.genx/templates/server/ai-service-web-handler.kts.hbs +394 -0
  12. package/.genx/tests/contracts/ai/ai-resolver-cases.json +6807 -0
  13. package/.genx/tests/contracts/ai/ui-config-ai.schema.json +203 -0
  14. package/.genx/tests/fixtures/ai-config-parse-breakers.json +16 -0
  15. package/.genx/tests/fixtures/ai-config.json +41 -0
  16. package/.genx/versions.json +3 -3
  17. package/.github/workflows/build.yml +20 -4
  18. package/CHANGELOG.md +55 -43
  19. package/README.md +139 -0
  20. package/bdd-tests/build.gradle.kts +1 -1
  21. package/bdd-tests/gradle/wrapper/gradle-wrapper.properties +1 -1
  22. package/bdd-tests/gradle.properties +0 -1
  23. package/bdd-tests/settings.gradle.kts +2 -2
  24. package/client-tmp/angular/package.json +3 -0
  25. package/client-tmp/react/.oxfmtrc.json +3 -0
  26. package/client-tmp/react/package.json +6 -2
  27. package/client-tmp/web-components/package.json +3 -0
  28. package/client-tmp/web-components/settings.gradle.kts +0 -22
  29. package/gradle/wrapper/gradle-wrapper.properties +1 -1
  30. package/package.json +1 -1
  31. package/server/build.gradle.kts +8 -1
  32. package/server/gradle/wrapper/gradle-wrapper.properties +1 -1
  33. package/server/gradle.properties +3 -3
  34. package/server/settings.gradle.kts +2 -2
  35. package/server/{{appName}}-app/src/main/genesis/scripts/genesis-router.kts +10 -0
  36. package/server/{{appName}}-app/src/test/kotlin/global/genesis/EventHandlerTest.kt +1 -1
@@ -0,0 +1,203 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
3
+ "$id": "https://genesis.global/schemas/create/ui-config-ai.schema.json",
4
+ "title": "ui.config.ai",
5
+ "version": "1.6.0",
6
+ "description": "The AI chat block a generated app reads from ui.config (GENC-1601 contract C-8). Produced by the archive service's ai-resolver from the project payload, and copied VERBATIM into the seed — both sides pin this file's digest, so a change here is a deliberate version bump, not an edit. It carries WHAT the chat may do and nothing about WHERE it talks: no url, endpoint, key or budget field exists, and additionalProperties is false everywhere so one cannot be added by accident. That is load-bearing — this block is written into the generated app's genesis-create.json and ships in the exported project, so a Create endpoint or key here would leave the box.",
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "required": ["enabled", "vendor", "tier", "systemPrompt", "resources"],
10
+ "properties": {
11
+ "enabled": {
12
+ "type": "boolean",
13
+ "description": "Emitted only for a project that selected AI chat; the block is absent entirely otherwise, so a project without AI stays byte-identical (C57)."
14
+ },
15
+ "vendor": {
16
+ "enum": ["gemini", "anthropic"],
17
+ "description": "Default 'gemini' (R-7). The generated app resolves a provider for it; the endpoint is supplied by the build environment, never from here."
18
+ },
19
+ "tier": {
20
+ "enum": ["low", "high", "reasoning"],
21
+ "description": "Default 'high' (R-7). Maps to a model through the foundation-ui tier table (C-3), so no model id appears in a generated project."
22
+ },
23
+ "systemPrompt": {
24
+ "type": "string",
25
+ "minLength": 1,
26
+ "description": "The assistant's standing instruction. Derived deterministically from the project, so regenerating an unchanged project produces an unchanged file."
27
+ },
28
+ "resources": {
29
+ "type": "array",
30
+ "uniqueItems": true,
31
+ "description": "What the chat may read and call, by the name the platform actually registers. A name that matches nothing is dropped silently by the runtime (C34), so these are derived from the project rather than typed by hand. One entry per name: the chat tolerates a duplicate, but MCP's enableMcp refuses the whole script over one, so the resolver dedupes and both consumers can trust it.",
32
+ "items": {
33
+ "type": "object",
34
+ "additionalProperties": false,
35
+ "required": ["name", "kind", "context"],
36
+ "properties": {
37
+ "name": {
38
+ "type": "string",
39
+ "pattern": "^[A-Z][A-Z0-9_]*$",
40
+ "maxLength": 64,
41
+ "description": "REQ_<NAME> for a request-server resource, EVENT_<NAME> for an event handler, and a dataserver query's own name, which is neither (C-15A.5 K-2). Never the bare MCP resource name (C34). At most 64 characters: the assistant's tool name is this name lowercased, and vendors reject longer tool names."
42
+ },
43
+ "kind": {
44
+ "enum": ["request", "event", "query"],
45
+ "description": "'request' is a read through the request server; 'event' is a write through an event handler; 'query' is a read of a dataserver query's live rows (C-15). A seed passes 'query' on only when its UI reads it (C-15A.5)."
46
+ },
47
+ "op": {
48
+ "enum": ["insert", "modify", "delete", "custom"],
49
+ "description": "Events only, in the platform's WIRE vocabulary. The project meta says create/update/delete; the resolver maps create→insert and update→modify so nothing downstream knows two names for one thing."
50
+ },
51
+ "context": {
52
+ "type": "string",
53
+ "minLength": 1,
54
+ "description": "One line telling the model what this resource is for."
55
+ },
56
+ "maxRows": {
57
+ "type": "integer",
58
+ "minimum": 1,
59
+ "maximum": 1000,
60
+ "description": "Requests and queries only; the resolver emits 50. A row cap in the host bridge applies regardless — this is guidance, not enforcement."
61
+ },
62
+ "shape": {
63
+ "enum": ["row"],
64
+ "description": "Custom events only (C-18.3). 'row': the handler's code was proven to change one existing row of entity, looked up by its primary key, and optionally insert rows elsewhere. A new meaning gets a new value, never a looser 'row'."
65
+ },
66
+ "entity": {
67
+ "type": "string",
68
+ "pattern": "^[A-Z0-9_]+$",
69
+ "description": "The table a resource acts on. Allowed on any resource with that one meaning (C-8); required with shape, where the bridge reads the row through REQ_<entity>."
70
+ },
71
+ "key": {
72
+ "type": "array",
73
+ "minItems": 1,
74
+ "uniqueItems": true,
75
+ "items": { "type": "string", "pattern": "^[A-Z0-9_]+$", "not": { "enum": ["RECORD_ID", "TIMESTAMP"] } },
76
+ "description": "The table's primary-key fields, in primary-key order. On a row action, the row the handler loads is the row the review shows; on a modify or a delete, the row the write names. RECORD_ID and TIMESTAMP are the platform's own and never a key."
77
+ },
78
+ "inputs": {
79
+ "type": "array",
80
+ "items": {
81
+ "type": "object",
82
+ "additionalProperties": false,
83
+ "required": ["field", "required"],
84
+ "properties": {
85
+ "field": { "type": "string", "pattern": "^[A-Z0-9_]+$" },
86
+ "required": { "const": true }
87
+ }
88
+ },
89
+ "description": "The fields the handler's code reads from the request beyond the key. May be empty. Every input is required in v1, and the constant is written so the bridge checks it rather than assumes it."
90
+ },
91
+ "effects": {
92
+ "type": "array",
93
+ "minItems": 1,
94
+ "items": {
95
+ "type": "object",
96
+ "additionalProperties": false,
97
+ "required": ["op", "table"],
98
+ "properties": {
99
+ "op": { "enum": ["modify", "insert"] },
100
+ "table": { "type": "string", "pattern": "^[A-Z0-9_]+$" }
101
+ }
102
+ },
103
+ "contains": { "required": ["op"], "properties": { "op": { "const": "modify" } } },
104
+ "minContains": 1,
105
+ "maxContains": 1,
106
+ "description": "One per write call in the handler's code, in code order: exactly one modify, of entity, and any inserts."
107
+ },
108
+ "customCode": {
109
+ "type": "object",
110
+ "additionalProperties": false,
111
+ "required": ["alsoWrites", "listComplete"],
112
+ "properties": {
113
+ "alsoWrites": {
114
+ "type": "array",
115
+ "uniqueItems": true,
116
+ "items": { "type": "string", "pattern": "^[A-Z0-9_]+$" },
117
+ "description": "Tables other than its own that the op's custom code provably writes to, in the order it first writes each. A floor, never the whole story on its own."
118
+ },
119
+ "listComplete": {
120
+ "type": "boolean",
121
+ "description": "True only when every write, call and way out of the code was accounted for, so alsoWrites names every other table it writes."
122
+ }
123
+ },
124
+ "description": "A CRUD event whose handler runs custom code (C-18.D): the review tells the user that the code can do more than save the values shown."
125
+ },
126
+ "references": {
127
+ "type": "array",
128
+ "items": {
129
+ "type": "object",
130
+ "additionalProperties": false,
131
+ "required": ["resource", "fields"],
132
+ "properties": {
133
+ "resource": { "type": "string", "pattern": "^REQ_[A-Z0-9_]+$" },
134
+ "fields": {
135
+ "type": "array",
136
+ "minItems": 1,
137
+ "items": {
138
+ "type": "object",
139
+ "additionalProperties": false,
140
+ "required": ["field", "targetField"],
141
+ "properties": {
142
+ "field": { "type": "string", "pattern": "^[A-Z0-9_]+$" },
143
+ "targetField": { "type": "string", "pattern": "^[A-Z0-9_]+$" }
144
+ }
145
+ }
146
+ }
147
+ }
148
+ },
149
+ "description": "Declared references of this write, for the bridge's existence check. Absent on a config older than this version, when the app's seed does not declare references, when an upstream pre-resolved block omits it, or when Create had no fields list for the table; [] means the table declares none. Only an insert or modify event carries it. Two rules the schema cannot say: a field appears at most once across all groups, and a group's targetField values are distinct; the resolver guarantees both by construction and its resourceProblem checks them on the pre-resolved path."
150
+ }
151
+ },
152
+ "$comment": "The kind decides the rest, and the name's prefix must fit it, as in the foundation-ui assistant's validateGenesisAiConfig, which blocks the WHOLE assistant on any mismatch. These rules mirror it, with two things a schema cannot say: two resources sharing a name but differing in another field pass uniqueItems (it only rejects identical items) yet block the assistant, which is why the resolver dedupes by name; and enabled false is valid here because it is a deliberate off, not a block the assistant will run. A row action (shape) has three more the schema cannot say, enforced by the resolver and mirrored by the bridge's rowActionProblem: the modify effect's table is entity; no key field is also an input; and REQ_<entity> is a request resource in the same block.",
153
+ "allOf": [
154
+ {
155
+ "description": "A request is a REQ_ name, and no op.",
156
+ "if": { "properties": { "kind": { "const": "request" } } },
157
+ "then": { "properties": { "name": { "pattern": "^REQ_[A-Z0-9_]+$" } }, "not": { "required": ["op"] } }
158
+ },
159
+ {
160
+ "description": "An event is an EVENT_ name, an op, and no maxRows.",
161
+ "if": { "properties": { "kind": { "const": "event" } } },
162
+ "then": { "properties": { "name": { "pattern": "^EVENT_[A-Z0-9_]+$" } }, "required": ["op"], "not": { "required": ["maxRows"] } }
163
+ },
164
+ {
165
+ "description": "A query is a name that is neither a request's nor an event's, and no op (C-15A.5 K-2, K-3).",
166
+ "if": { "properties": { "kind": { "const": "query" } } },
167
+ "then": { "properties": { "name": { "not": { "pattern": "^(REQ|EVENT)_" } } }, "not": { "required": ["op"] } }
168
+ },
169
+ {
170
+ "description": "A row action's companions ride a custom op with shape only, and with shape all four are required. key may also ride a modify or a delete (below).",
171
+ "if": { "required": ["op", "shape"], "properties": { "op": { "const": "custom" } } },
172
+ "then": { "required": ["entity", "key", "inputs", "effects"] },
173
+ "else": {
174
+ "not": {
175
+ "anyOf": [{ "required": ["shape"] }, { "required": ["inputs"] }, { "required": ["effects"] }]
176
+ }
177
+ }
178
+ },
179
+ {
180
+ "description": "key rides a custom op with shape (above) or a modify or delete event (C-19.1). op is required in each branch: an absent op would satisfy it vacuously.",
181
+ "if": { "required": ["key"] },
182
+ "then": {
183
+ "anyOf": [
184
+ { "required": ["op", "shape"], "properties": { "op": { "const": "custom" } } },
185
+ { "required": ["op"], "properties": { "op": { "enum": ["modify", "delete"] } } }
186
+ ]
187
+ }
188
+ },
189
+ {
190
+ "description": "customCode rides an insert, modify or delete event only. op is required here: an absent op would satisfy an if on op vacuously.",
191
+ "if": { "required": ["customCode"] },
192
+ "then": { "required": ["op"], "properties": { "op": { "enum": ["insert", "modify", "delete"] } } }
193
+ },
194
+ {
195
+ "description": "references ride an insert or modify event only (C-17.3). op is required in the if: an absent op would satisfy it vacuously.",
196
+ "if": { "required": ["op"], "properties": { "op": { "enum": ["insert", "modify"] } } },
197
+ "else": { "not": { "required": ["references"] } }
198
+ }
199
+ ]
200
+ }
201
+ }
202
+ }
203
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "ai": {
3
+ "enabled": true,
4
+ "vendor": "gemini",
5
+ "tier": "high",
6
+ "systemPrompt": "Text Handlebars cannot even parse: {{#if}} opens a block with no argument, {{{ never closes, and {{#each items}} is never closed.",
7
+ "resources": [
8
+ {
9
+ "name": "REQ_TRADE",
10
+ "kind": "request",
11
+ "context": "Trades, {{#unless}}.",
12
+ "maxRows": 50
13
+ }
14
+ ]
15
+ }
16
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "ai": {
3
+ "enabled": true,
4
+ "vendor": "gemini",
5
+ "tier": "high",
6
+ "systemPrompt": "You are the assistant for {{appName}}{{! a comment }}. Keep \\{{this}}, {{#if AI.enabled}}shown{{/if}}, {{#if x}}hidden{{/if}} and {{{that}}} literal; \"quotes\", </script>, ünïcödé and the text \\u007b must survive.",
7
+ "resources": [
8
+ {
9
+ "name": "REQ_TRADE",
10
+ "kind": "request",
11
+ "context": "Trades booked in {{appName}}.",
12
+ "maxRows": 50
13
+ },
14
+ {
15
+ "name": "EVENT_TRADE_INSERT",
16
+ "kind": "event",
17
+ "op": "insert",
18
+ "context": "Book a new trade.",
19
+ "customCode": { "alsoWrites": ["POSITION"], "listComplete": false },
20
+ "references": [
21
+ { "resource": "REQ_COUNTERPARTY", "fields": [{ "field": "COUNTERPARTY_ID", "targetField": "COUNTERPARTY_ID" }] }
22
+ ]
23
+ },
24
+ {
25
+ "name": "EVENT_TRADE_MODIFY",
26
+ "kind": "event",
27
+ "op": "modify",
28
+ "context": "Change a trade.",
29
+ "references": [
30
+ { "resource": "REQ_COUNTERPARTY", "fields": [{ "field": "COUNTERPARTY_ID", "targetField": "COUNTERPARTY_ID" }] }
31
+ ]
32
+ },
33
+ {
34
+ "name": "REQ_COUNTERPARTY",
35
+ "kind": "request",
36
+ "context": "Counterparties a trade can name.",
37
+ "maxRows": 50
38
+ }
39
+ ]
40
+ }
41
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "UI": "15.39.0",
3
- "GSF": "10.0.0-beta4",
4
- "Auth": "10.0.0-beta4"
2
+ "UI": "15.50.0",
3
+ "GSF": "8.15.14",
4
+ "Auth": "8.15.3"
5
5
  }
@@ -12,15 +12,17 @@ concurrency:
12
12
  jobs:
13
13
  # The `build` job below covers React end to end (gradle build, lint, unit + e2e) for
14
14
  # the defaults app. This job keeps every framework's generator honest: for each one it
15
- # generates the defaults app, the full-routes fixture app and an FDC3-channels app,
16
- # and runs the ox lint gates over all three.
15
+ # generates the defaults app, the full-routes fixture app and an FDC3-channels app, plus
16
+ # for React an app with the AI chat on, and runs the ox lint gates over each.
17
17
  #
18
18
  # BUILD=1 adds `tsc --noEmit` and the production build per app, which is what makes a
19
19
  # broken generated tile fail here. Lint alone does not type-check, and the app the
20
20
  # `build` job below compiles is the defaults one, which generates no tiles at all, so
21
21
  # before this nothing compiled a generated tile: a grid tile passed a prop the React
22
22
  # wrapper's types did not declare and CI stayed green while `npm run build` failed for
23
- # anyone scaffolding with routes.
23
+ # anyone scaffolding with routes. For the AI app it also checks that the assistant and
24
+ # its bubble are built into a chunk of their own, and into no other, and that the seed's
25
+ # AI declaration holds the baseline and names nothing the installed assistant does not read.
24
26
  generated-apps:
25
27
  strategy:
26
28
  fail-fast: false
@@ -37,9 +39,15 @@ jobs:
37
39
  with:
38
40
  node-version: 22
39
41
 
40
- - name: Generate and lint ${{ matrix.framework }} apps (default + full + fdc3)
42
+ - name: Generate and lint ${{ matrix.framework }} apps (default + full + fdc3, and ai for React)
41
43
  run: ./.genx/scripts/generate-test-apps.sh ${{ matrix.framework }}
42
44
 
45
+ # Generates its own React and web-components apps, so one leg is enough. This job has no Java
46
+ # or artifactory credentials, so it runs the emission checks only; the build job adds GRADLE=1.
47
+ - name: Check what the AI chat path emits
48
+ if: matrix.framework == 'react'
49
+ run: ./.genx/scripts/check-ai-emission.sh
50
+
43
51
  build:
44
52
  env:
45
53
  genesisArtifactoryUser: ${{ secrets.JFROG_LIBS_RELEASE_CLIENT_RO_USER }}
@@ -79,6 +87,14 @@ jobs:
79
87
  working-directory: /tmp/testapp
80
88
  run: ./gradlew ${GRADLE_PARAMS} build --info
81
89
 
90
+ # The AI chat proxy is a script, and scripts compile only when the router starts, so no build
91
+ # compiles it. This one builds an AI app, runs the security scan on it and compiles both
92
+ # vendors' proxies against this Genesis version. It picks up GRADLE_PARAMS from the job.
93
+ - name: Build, scan and compile the AI chat proxy
94
+ env:
95
+ GRADLE: 1
96
+ run: ./.genx/scripts/check-ai-emission.sh
97
+
82
98
  - name: Lint UI
83
99
  working-directory: /tmp/testapp/client
84
100
  run: npm run lint
package/CHANGELOG.md CHANGED
@@ -1,48 +1,60 @@
1
1
  # Changelog
2
2
 
3
- ## [5.27.2-prerelease.3](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.27.2-prerelease.2...v5.27.2-prerelease.3) (2026-09-29)
4
-
5
-
6
- ### Bug Fixes
7
-
8
- * updating server version information for Auth [PSD-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 3130813
9
-
10
- ## [5.27.2-prerelease.2](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.27.2-prerelease.1...v5.27.2-prerelease.2) (2026-09-29)
11
-
12
-
13
- ### Bug Fixes
14
-
15
- * updating server version information for GSF [PSD-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 7dfb94d
16
-
17
- ## [5.27.2-prerelease.1](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.27.1...v5.27.2-prerelease.1) (2026-09-29)
18
-
19
-
20
- ### Bug Fixes
21
-
22
- * address release workflow review comments 67ce8c3
23
- * address second round of release workflow review comments 4a1c5f5
24
- * backport main to prerelease [PSD-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) (#599) 841793f
25
- * backport main to prerelease [PSD-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) (#616) 545eb21
26
- * Bump BDD framework (bddVersion) from 3.5.30 to 3.5.73 GENC-0 (#609) 4b8bb58
27
- * **client:** resolve the client buildscript from Artifactory (GSF-8370) 489b951
28
- * **client:** resolve the client buildscript from Artifactory (GSF-8370) (#633) ede81ee
29
- * default dist-tag to latest when semantic-release channel is null ce5a216
30
- * merge main into prerelease GENC-1362 (#589) c7eed71
31
- * resolve from Artifactory before Maven Central in every module (GSF-8370) 2a263c2
32
- * **server:** query the Plugin Portal before Maven Central (GSF-8370) 725b377
33
- * **server:** resolve from Artifactory before Maven Central (GSF-8370) 64f1d3f
34
- * **server:** resolve from Artifactory before Maven Central (GSF-8370) (#628) f29106c
35
- * updating server version information for Auth [PSD-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) e2f022d
36
- * updating server version information for Auth [PSD-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) fe2dc7d
37
- * updating server version information for Auth [PSD-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 4204083
38
- * updating server version information for Auth [PSD-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 0ea031d
39
- * updating server version information for GSF [PSD-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 57d68e9
40
- * updating server version information for GSF [PSD-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 3755030
41
- * updating server version information for GSF [PSD-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) fff92ca
42
- * updating server version information for GSF [PSD-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) b1155f0
43
- * updating server version information for GSF [PSD-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 2b31d47
44
- * use jakarta.inject in the scaffolded EventHandlerTest (GSF-7926) 0cf7b88
45
- * use jakarta.inject in the scaffolded EventHandlerTest (GSF-7926) (#634) b5af5bf
3
+ ## [5.28.0](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.27.1...v5.28.0) (2026-10-02)
4
+
5
+
6
+ ### Features
7
+
8
+ * add desktop and headless Genesis Start scripts to the client GENC-1609 2806180
9
+ * add the AI assistant to an AI app, in a chat bubble GENC-1611 (#638) 5a04d57
10
+ * add the AI chat panel to an AI app, loading the assistant the first time it opens GENC-1611 b6d31f5
11
+ * always depend on the AI assistant in a React app, so a preview base already has it GENC-1611 393c84b
12
+ * copy Create's schema 1.6.0 and cases 1.8.0, with modifies and deletes named by key GENC-1611 65f4621
13
+ * copy to the AI config only what the seed declares its assistant reads GENC-1611 b07cf57
14
+ * copy to the AI config only what the seed declares its assistant reads GENC-1611 (#642) 4385268
15
+ * give an AI app the assistant package and build it with AI switched on GENC-1611 660ca45
16
+ * keep a key only where it names a row, and check a modify's key through CI GENC-1611 c238ab3
17
+ * move Genesis Start to 0.1.15, the first launcher that can run headless GENC-1609 81136ae
18
+ * open the AI assistant from a chat bubble GENC-1611 212bce1
19
+ * pass queries, references and row actions to the AI chat GENC-1611 (#643) 92d7c5e
20
+ * pin UI 15.47.0 and pass its queries and references to the chat GENC-1611 37ebdfd
21
+ * pin UI 15.50.0 and pass row actions and keys to the chat GENC-1611 6aafccd
22
+ * ship the AI chat proxy with a React app that asks for it GENC-1609 4b67a7a
23
+ * ship the AI chat proxy with a React app that asks for it GENC-1609 (#636) 76dabeb
24
+ * write a row action's fields as the chat reads them, and check them through CI GENC-1611 fa0f782
25
+ * write the chat panel's configuration into an AI app GENC-1611 6eff2c2
26
+
27
+
28
+ ### Bug Fixes
29
+
30
+ * accept a lowercase content-type from a proxy GENC-1609 8002275
31
+ * allow the reasoning tier's models in the proxy GENC-1609 0a9c11d
32
+ * answer 424 when the AI key cannot be read, never an exception GENC-1609 6cdb268
33
+ * answer the chat panel as JSON whatever it accepts GENC-1609 9c3cbb7
34
+ * ask Google's repository only for the launcher's androidx artifacts, after Genesis's own GENC-1609 1788d9b
35
+ * bound what the proxy will parse from a request body GENC-1609 032f00b
36
+ * build the AI limits into the proxy so a rewritten sysdef cannot drop them GENC-1609 8144dec
37
+ * document only the environment route for the AI keys GENC-1609 f56f8e0
38
+ * fail the AI declaration check on a GENESIS_AI_CONSUMES it cannot use GENC-1611 35ab208
39
+ * give each user their own assistant session and check the right on every mount GENC-1611 aedc3e3
40
+ * hold Gemini to one candidate so the token cap is the real cap GENC-1609 f238852
41
+ * keep a bad key and failed calls out of replies and logs GENC-1609 2da7ed6
42
+ * keep the key out of vendor replies and bare model ids in the proxy GENC-1609 1865ad5
43
+ * move the UI packages to 15.45.0 GENC-1611 9f85173
44
+ * put the AI settings in the system definition the server reads GENC-1609 8082946
45
+ * raise the router body cap above the chat proxy's 5 MiB limit GENC-1609 42d8bca
46
+ * read the AI keys from the environment, never the system definition GENC-1609 32d5249
47
+ * refuse a nonsense AI_MAX_OUTPUT_TOKENS instead of guessing, and never clamp below 1 GENC-1609 ae9cadc
48
+ * refuse every model when the allow-list is empty GENC-1609 be10603
49
+ * refuse fallback models instead of vetting them GENC-1609 a32f906
50
+ * refuse Gemini's snake_case spellings of the capped fields GENC-1609 83401f7
51
+ * reply through the router's stateless JSON writer GENC-1609 8bc9148
52
+ * run chat calls outside a database transaction GENC-1609 97784a2
53
+ * say plainly that a sent vendor call runs to completion GENC-1609 7fa161f
54
+ * stop holding a thread for the whole vendor call GENC-1609 6ab3e9b
55
+ * stop logging every chat request with the caller's session GENC-1609 4c1eaf9
56
+ * use inject, not the deprecated injector, in the proxy GENC-1609 f0fae49
57
+ * warn against requiresAuth and make the auth checks exact GENC-1609 399ea9b
46
58
 
47
59
  ## [5.27.1](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.27.0...v5.27.1) (2026-09-24)
48
60
 
package/README.md CHANGED
@@ -1,6 +1,92 @@
1
1
  # {{appName}}
2
2
 
3
3
  {{{description}}}
4
+ {{#if AI.enabled}}
5
+
6
+ ## AI chat
7
+
8
+ This application includes an AI chat: an assistant in the app, and the endpoint it calls,
9
+ `/gwf/ai-service/<vendor>/chat`, which talks to **your** AI vendor with **your** key. Nothing is
10
+ routed through Genesis.
11
+
12
+ **Set your key where the server runs, never in a tracked file in this project.** The chat endpoint
13
+ reads it from the environment that starts the server, so set the one for your vendor there, then
14
+ restart it (it is read once at boot):
15
+
16
+ ```
17
+ AI_ANTHROPIC_API_KEY=sk-ant-...
18
+ AI_GEMINI_API_KEY=...
19
+ ```
20
+
21
+ Do not write the key into `docker-compose.yml`, which is committed with the project. Pass it through
22
+ from the shell (`environment: [AI_ANTHROPIC_API_KEY]`) or keep it in an untracked `.env` file
23
+ (`env_file: .env`), which the project's `.gitignore` already excludes.
24
+
25
+ **Never set it as a system-definition item.** Genesis turns every `GENESIS_SYSDEF_*` variable into
26
+ one, and writes it in plain text into the files a build or an install renders
27
+ (`server/{{appName}}-app/build/genesis/rendered-templates/generated-system-definition.json`, and
28
+ `generated/cfg/generated-system-definition.json` under the Genesis home, including the one under
29
+ `build/`), into `~/.bashrc` inside the app's container, and into the logs at TRACE. So
30
+ `GENESIS_SYSDEF_AI_ANTHROPIC_API_KEY` and `GENESIS_SYSDEF_AI_GEMINI_API_KEY`, which earlier versions
31
+ of this app read, are refused: the endpoint logs a warning and answers as if no key were set. If you
32
+ set one, remove it, delete the server's `build/` directory and the Genesis home's `generated/cfg`,
33
+ and rotate the key: every copy of those files holds it. `GENESIS_ENCRYPTED_SYSDEF_...` is not read
34
+ either.
35
+
36
+ **Who can use it.** Calling the chat endpoint needs the `AI_CHAT` right. An app generated by Genesis
37
+ Create with AI Chat selected already has it: the `AI_CHAT_USER` profile holds the right, and `admin`
38
+ is in that profile. Add other users to that profile, or grant the right through any other profile.
39
+ Otherwise, create the `AI_CHAT` right and grant it the same way.
40
+
41
+ **The assistant.** Signed-in users get a chat bubble at the bottom right of every page, which opens
42
+ the assistant and can be dragged out of the way. Each user gets a conversation of their own, kept until
43
+ the page is reloaded. A user without the `AI_CHAT` right is told so in a banner when they open it,
44
+ and the assistant sends nothing. Rights are read at sign-in, so sign in again after a change. The
45
+ assistant can read the data this project exposes to it: which resources, and what it is told about
46
+ each, is in `client/src/ai/generated/ai-config.json`. That folder is rewritten every time the project
47
+ is generated, so do not edit it; add your own tools in `client/src/ai/extensions/index.ts`, which
48
+ shows how. The client's `build` and `dev` scripts build it with `GENX_ENABLE_AI=true`; without that
49
+ the banner says AI is switched off. Whenever the assistant is blocked, its message box reads "AI usage
50
+ limit reached", whatever the reason; the banner above it gives the real one.
51
+
52
+ **Bound what it can do.** The limits this app was generated with are built into the chat endpoint,
53
+ `server/{{appName}}-app/src/main/genesis/scripts/ai-service-web-handler.kts`. Override either with a
54
+ system-definition item of the same name, set from the environment as `GENESIS_SYSDEF_<name>` (they
55
+ are not secret):
56
+
57
+ - `AI_ALLOWED_MODELS` (`GENESIS_SYSDEF_AI_ALLOWED_MODELS`) lists the models a request may ask for.
58
+ **Set but empty, it refuses every model.**
59
+ - `AI_MAX_OUTPUT_TOKENS` (`GENESIS_SYSDEF_AI_MAX_OUTPUT_TOKENS`) caps the output a single call can
60
+ request (Gemini is also held to one candidate).
61
+
62
+ Fallback models and streamed replies are refused. Vendor server-side tools (for example Anthropic web
63
+ search or Gemini Google Search grounding) are **not** filtered: anyone with `AI_CHAT` can request
64
+ them, and they are billed to your key.
65
+
66
+ **If the chat cannot answer.** With no key configured the endpoint answers **424** with the code
67
+ `NO_API_KEY` (`BAD_API_KEY` if the key contains characters an API key cannot have). A key the vendor
68
+ rejects, revoked or wrong, comes back as the vendor's own error, usually 401 or 400. The assistant
69
+ tries a failed call three times in all and then shows only a generic error, so check the response in
70
+ your browser's developer tools.
71
+
72
+ **Things to know before deploying.**
73
+
74
+ - For the chat, the server accepts request bodies up to 6 MiB instead of the default 256 KiB. The chat
75
+ endpoint itself takes up to 5 MiB, and the extra room lets it answer a turn just over that with its
76
+ own `REQUEST_TOO_LARGE` code. The 6 MiB limit is router-wide and applies before login, so it covers
77
+ every endpoint, including unauthenticated ones.
78
+ - The app's Docker image puts nginx in front of the server, and its `nginx.conf` sets no
79
+ `client_max_body_size`, so nginx's 1 MiB default applies: raise it to `6m`, or chat turns over
80
+ 1 MiB are refused before they reach the server. The Genesis Gradle plugin rewrites that
81
+ `nginx.conf` on every build, so don't edit it. Add a file under `/etc/nginx/conf.d/` containing
82
+ `client_max_body_size 6m;` instead (`nginx.conf` includes `conf.d/*.conf` inside its `http` block),
83
+ for example from a Dockerfile you supply through the plugin's `customDockerfile` setting, which
84
+ replaces the generated one. Any other reverse proxy needs the same limit.
85
+ - The server's default CORS policy accepts any origin with credentials, and the session cookie is
86
+ `SameSite=Lax`. A page on the same site as this app (a sibling subdomain, for example) can therefore
87
+ call the chat as a logged-in user who holds `AI_CHAT`, and spend your key. Serve the app from a
88
+ domain nothing else shares, or restrict CORS, before exposing it.
89
+ {{/if}}
4
90
 
5
91
  {{!
6
92
 
@@ -57,6 +143,59 @@ When first opening the project, if you receive a notification from IntelliJ IDE
57
143
  The Web client for this project can be found [here](./client/README.md). It is built using Genesis's next
58
144
  generation web development framework, which is based on Web Components.
59
145
 
146
+ ## Running the application
147
+
148
+ Genesis Start runs the server for you. From `client/`:
149
+
150
+ ```
151
+ npm run genesis-start # opens the Genesis Start desktop launcher
152
+ npm run genesis-start:headless # the same launcher with no window, driven over a REST API on port 18080
153
+ ```
154
+
155
+ On Windows, npm runs these scripts in `cmd.exe`, where `./gradlew` does not work. From `client/`, run
156
+ `cd ..\server && gradlew.bat genesisStart` instead, adding the `-P` flags from `package.json` for
157
+ headless.
158
+
159
+ **Headless** is for a build machine or a remote development environment:
160
+
161
+ - `npm run genesis-start:headless` returns once the launcher is up and prints
162
+ `Genesis Start started with PID <n>`. The launcher keeps running in the background, even after the
163
+ terminal closes, and Ctrl+C does not stop it.
164
+ - To stop it, stop the app's processes first:
165
+ `curl -X POST http://localhost:18080/api/processes/application/stop-all`, then wait until
166
+ `curl http://localhost:18080/api/processes` shows none of them `RUNNING`, and stop any utility still
167
+ running with `curl -X POST http://localhost:18080/api/processes/<id>/stop`. Only then `kill <n>`.
168
+ Stopping the launcher first leaves every process it started running and holding its ports.
169
+ - It takes over every Genesis process already running on the machine when it starts, so stop-all
170
+ stops those too, even another app's.
171
+ - It logs to `server/build/genesis-start/output.log` and `error.log`; if it exits within five seconds,
172
+ Gradle says so and points there. Each process it starts logs under the app's Genesis home, and
173
+ `GET /api/processes/{processId}/log` returns that log.
174
+ - Check it is up with `curl http://localhost:18080/api/health`. The API is described at
175
+ `http://localhost:18080/api/docs`.
176
+ - `npm run genesis-start:write-script` writes `server/build/genesis-start/start.sh` (`start.bat` on
177
+ Windows), which runs the same headless launcher without Gradle, in the foreground, logging to the
178
+ same two files. Ctrl+C stops only the launcher, so stop the processes first, as above.
179
+ - The script, and the command line Gradle prints as it starts the launcher (which `ps` also shows),
180
+ can hold the database password in plain text.
181
+
182
+ **The REST API has no authentication and listens on every network interface.** Anyone who can reach
183
+ port 18080 can:
184
+
185
+ - start and stop the app's processes, and apply schema changes to its database (`/api/bootstrap`
186
+ runs Remap);
187
+ - write rows into any table: `/api/import` loads files from this machine with SendIt, and a file's
188
+ name picks the table, so a `USER.csv` or `PROFILE_USER.csv` can add users or give them profiles,
189
+ and with them rights such as `AI_CHAT`. It takes a path; 0.1.15 refuses an uploaded file, but a
190
+ later version may not;
191
+ - run the app's scripts with any arguments and working directory (`/api/scripts/run`), and type into
192
+ a running utility (`/api/processes/{processId}/input`).
193
+
194
+ Run headless only on a machine and network you trust, or block port 18080 from anything else. A web
195
+ page you visit on that machine may be able to reach `localhost:18080` too: the API checks no origin,
196
+ and it takes JSON sent as plain text, so a page can stop or start processes and run the app's scripts
197
+ without a CORS preflight.
198
+
60
199
  # License
61
200
 
62
201
  This is free and unencumbered software released into the public domain. For full terms, see [LICENSE](./LICENSE)
@@ -6,6 +6,7 @@ plugins {
6
6
  description = "{{appName}} BDD Testing Framework"
7
7
 
8
8
  repositories {
9
+ mavenCentral()
9
10
  maven {
10
11
  val repoUrl = if (properties["useDevRepo"] == "true") {
11
12
  "https://genesisglobal.jfrog.io/genesisglobal/dev-repo"
@@ -18,7 +19,6 @@ repositories {
18
19
  password = properties["genesisArtifactoryPassword"].toString()
19
20
  }
20
21
  }
21
- mavenCentral()
22
22
  mavenLocal {
23
23
  // VERY IMPORTANT!!! EXCLUDE AGRONA AS IT IS A POM DEPENDENCY AND DOES NOT PLAY NICELY WITH MAVEN LOCAL!
24
24
  content {
@@ -1,6 +1,6 @@
1
1
  distributionBase=GRADLE_USER_HOME
2
2
  distributionPath=wrapper/dists
3
- distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
3
+ distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
4
4
  networkTimeout=10000
5
5
  validateDistributionUrl=true
6
6
  zipStoreBase=GRADLE_USER_HOME
@@ -4,5 +4,4 @@ org.gradle.configuration-cache=false
4
4
  org.gradle.parallel=true
5
5
  org.gradle.caching=true
6
6
  bddVersion=3.5.73
7
- useDevRepo=true
8
7