@saltcorn/bootstrap-prompt-theme 0.1.3 → 0.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 (3) hide show
  1. package/agent-skill.js +63 -90
  2. package/index.js +114 -0
  3. package/package.json +1 -1
package/agent-skill.js CHANGED
@@ -55,11 +55,12 @@ CRITICAL RULES — never break these:
55
55
  - Never set z-index on .card or page content elements — stacking context on page content is what causes navbar dropdowns to be obscured.
56
56
  - Do not override --bs-zindex-dropdown, --bs-zindex-fixed, or .dropdown-menu z-index.
57
57
  - Dropdowns must always be fully visible and on top of all other page content, including cards, sections, and containers.
58
+ - Always pair background and foreground colors: whenever you set a background color on any element — form controls (.form-control, .form-select), dropdowns (.dropdown-menu, .dropdown-item), navbar (.navbar, .nav-link), cards, or any container — you must also explicitly set a readable text/foreground color on that element and its direct text children. Bootstrap does not reliably propagate text color into all components, especially in dark mode. Common failure cases: dark .dropdown-menu background with light-mode .dropdown-item text color (text disappears); dark .form-control background with inherited body text color (typed text invisible); dark navbar with unset .nav-link color. Always set both background and text color together. When in doubt, also set the matching --bs-* token pair (e.g. --bs-dropdown-bg with --bs-dropdown-link-color, --bs-body-bg with --bs-body-color).
58
59
 
59
60
  IMAGES: The user may attach images to the conversation. Use them as design inspiration — extract colors, typography style, spacing feel, or overall mood and translate that into the CSS overlay. If the user attaches an image without further instruction, derive a theme from it. If they describe what they want alongside the image, use the image to inform the details.
60
61
 
61
62
  LAYOUT CONFIG: Besides CSS, you can control structural layout options via set_layout_config:
62
- - mode: "light" | "dark" — Bootstrap color mode applied to <html data-bs-theme>
63
+ - mode: "light" | "dark" — Bootstrap color mode applied to <html data-bs-theme>. IMPORTANT: whenever the user asks for a dark theme or dark mode, you MUST call set_layout_config with mode: "dark". Do not only adjust CSS colors — without mode: "dark" Bootstrap's own dark-mode component styles will not activate and the result will look wrong.
63
64
  - menu_style: "Top Navbar" | "Side Navbar" | "No Menu"
64
65
  - colorscheme: navbar color class pair, e.g. "navbar-dark bg-dark", "navbar-light bg-light", "navbar-dark bg-primary"
65
66
  - fixed_top: true | false — fix navbar to top of viewport
@@ -67,78 +68,9 @@ LAYOUT CONFIG: Besides CSS, you can control structural layout options via set_la
67
68
  - top_pad: 0–5 — Bootstrap spacing scale for top padding on page sections
68
69
  - in_card: true | false — wrap page body in a Bootstrap card
69
70
  Only call set_layout_config when you want to change structural layout, separate from CSS. Call both tools when a request requires both structural and CSS changes.
71
+ When you change menu_style, the tool response will include a page_structure field with rendered HTML of the new layout. Read it to understand the actual element hierarchy and CSS selectors before writing any CSS for that layout.
70
72
 
71
- PAGE STRUCTURE: A typical rendered Saltcorn page looks like this (abridged). Use it to understand element hierarchy, class names, and selectors when writing CSS:
72
- \`\`\`html
73
- <html lang="en" data-bs-theme="light">
74
- <head>
75
- <meta charset="utf-8">
76
- <meta name="viewport" content="width=device-width, initial-scale=1">
77
- <link href="/plugins/public/bootstrap-prompt-theme/bootstrap.min.css" rel="stylesheet">
78
- <link rel="stylesheet" href="/plugins/public/bootstrap-prompt-theme/sidebar-3.css">
79
- <link rel="stylesheet" href="/plugins/public/bootstrap-prompt-theme/overlay.css">
80
- <link href="/static_assets/.../saltcorn.css" rel="stylesheet">
81
- <script>var _sc_globalCsrf = "<csrf-token>", _sc_version_tag = "...", _sc_lightmode = "light";</script>
82
- </head>
83
- <body id="page-top" class="page_<pagename>">
84
- <div id="wrapper">
85
- <nav class="navbar d-print-none navbar-expand-md navbar-light bg-light" id="mainNav">
86
- <div class="container">
87
- <a class="navbar-brand" href="/">Saltcorn</a>
88
- <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive">
89
- <span class="navbar-toggler-icon"></span>
90
- </button>
91
- <div class="collapse navbar-collapse" id="navbarResponsive">
92
- <ul class="navbar-nav ms-auto my-2 my-lg-0">
93
- <li class="nav-item"><a class="nav-link" href="/table">Tables</a></li>
94
- <li class="nav-item dropdown">
95
- <a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown">Settings</a>
96
- <div class="dropdown-menu">
97
- <a class="dropdown-item" href="/admin">About application</a>
98
- <a class="dropdown-item" href="/plugins">Modules</a>
99
- </div>
100
- </li>
101
- <li class="nav-item dropdown">
102
- <a class="nav-link dropdown-toggle user-nav-section" href="#" data-bs-toggle="dropdown">User</a>
103
- <div class="dropdown-menu dropdown-menu-end">
104
- <a class="dropdown-item" href="/auth/settings">User settings</a>
105
- <a class="dropdown-item" href="/auth/logout">Logout</a>
106
- </div>
107
- </li>
108
- </ul>
109
- </div>
110
- </div>
111
- </nav>
112
- <div id="page-inner-content">
113
- <section class="page-section pt-2">
114
- <div class="container">
115
- <!-- admin edit bar (admin only) -->
116
- <div class="card p-1 mt-1 mb-3 d-print-none admin-edit-bar">
117
- <div class="card-body p-1">...</div>
118
- </div>
119
- </div>
120
- </section>
121
- <section class="page-section">
122
- <div class="container">
123
- <!-- page content, e.g. a table view -->
124
- <div class="table-responsive">
125
- <table class="table table-sm table-valign-middle">
126
- <thead><tr><th>Email</th><th>Role</th></tr></thead>
127
- <tbody><tr><td>admin@foo.com</td><td>1</td></tr></tbody>
128
- </table>
129
- </div>
130
- </div>
131
- </section>
132
- <section class="page-section">
133
- <div class="container">
134
- <div id="toasts-area" class="toast-container position-fixed top-0 end-0 p-2" style="z-index: 9999;"></div>
135
- </div>
136
- </section>
137
- </div>
138
- </div>
139
- </body>
140
- </html>
141
- \`\`\`
73
+ {{PAGE_STRUCTURE}}
142
74
 
143
75
  WORKFLOW:
144
76
  1. Call apply_css_overlay with the complete CSS — this is the only way to deliver CSS.
@@ -203,6 +135,18 @@ const savePluginConfig = async (plugin, patch) => {
203
135
  });
204
136
  };
205
137
 
138
+ const renderStructureSkeleton = (config) => {
139
+ try {
140
+ return require(join(__dirname, "index.js")).renderStructureSkeleton(config);
141
+ } catch (e) {
142
+ getState().log(
143
+ 4,
144
+ `bootstrap-prompt-theme: renderStructureSkeleton failed: ${e.message}`
145
+ );
146
+ return null;
147
+ }
148
+ };
149
+
206
150
  class GenerateBootstrapThemeSkill {
207
151
  static skill_name = "Generate Bootstrap Theme";
208
152
 
@@ -218,8 +162,26 @@ class GenerateBootstrapThemeSkill {
218
162
  return [];
219
163
  }
220
164
 
221
- systemPrompt() {
222
- return SYSTEM_PROMPT;
165
+ async systemPrompt() {
166
+ const plugin = await findPlugin();
167
+ const config = plugin?.configuration || {};
168
+ let pageStructure =
169
+ "PAGE STRUCTURE: (unavailable — could not render current layout)";
170
+ try {
171
+ const { renderPageSkeleton } = require(join(__dirname, "index.js"));
172
+ const html = renderPageSkeleton(config);
173
+ if (html)
174
+ pageStructure =
175
+ "PAGE STRUCTURE: The current page renders like this (with a sample list view, based on the active layout config). Use it to understand element hierarchy, class names, and selectors when writing CSS:\n```html\n" +
176
+ html +
177
+ "\n```";
178
+ } catch (e) {
179
+ getState().log(
180
+ 4,
181
+ `bootstrap-prompt-theme: systemPrompt renderPageSkeleton failed: ${e.message}`
182
+ );
183
+ }
184
+ return SYSTEM_PROMPT.replace("{{PAGE_STRUCTURE}}", pageStructure);
223
185
  }
224
186
 
225
187
  provideTools() {
@@ -281,30 +243,41 @@ class GenerateBootstrapThemeSkill {
281
243
  return pre(code(entries));
282
244
  },
283
245
  process: async (params) => {
246
+ const allowed = [
247
+ "mode",
248
+ "menu_style",
249
+ "colorscheme",
250
+ "fixed_top",
251
+ "fluid",
252
+ "top_pad",
253
+ "in_card",
254
+ ];
255
+ const patch = Object.fromEntries(
256
+ Object.entries(params).filter(([k]) => allowed.includes(k))
257
+ );
284
258
  const plugin = await findPlugin();
285
259
  if (plugin) {
286
- const allowed = [
287
- "mode",
288
- "menu_style",
289
- "colorscheme",
290
- "fixed_top",
291
- "fluid",
292
- "top_pad",
293
- "in_card",
294
- ];
295
- const patch = Object.fromEntries(
296
- Object.entries(params).filter(([k]) => allowed.includes(k))
297
- );
298
260
  await savePluginConfig(plugin, patch);
299
261
  }
300
- return { success: true };
262
+ const result = { success: true };
263
+ if (params.menu_style) {
264
+ const mergedConfig = { ...(plugin?.configuration || {}), ...patch };
265
+ result.page_structure = renderStructureSkeleton(mergedConfig);
266
+ }
267
+ return result;
301
268
  },
302
- renderToolResponse: async () =>
303
- div({ class: "text-success" }, "Layout configuration updated."),
269
+ renderToolResponse: async ({ page_structure }) =>
270
+ div(
271
+ { class: "text-success" },
272
+ "Layout configuration updated.",
273
+ page_structure
274
+ ? pre({ class: "mt-2 small" }, code(page_structure))
275
+ : ""
276
+ ),
304
277
  function: {
305
278
  name: "set_layout_config",
306
279
  description:
307
- "Set structural layout configuration for the Saltcorn UI. Only pass the parameters you want to change.",
280
+ "Set structural layout configuration for the Saltcorn UI. Only pass the parameters you want to change. When menu_style is set, the tool response includes a page_structure field containing rendered HTML of the new layout — use it to understand the correct selectors before writing CSS.",
308
281
  parameters: {
309
282
  type: "object",
310
283
  properties: {
package/index.js CHANGED
@@ -928,6 +928,118 @@ document.addEventListener('DOMContentLoaded', () => {
928
928
  ],
929
929
  });
930
930
 
931
+ const FAKE_MENU = [
932
+ {
933
+ section: "Main",
934
+ items: [
935
+ { label: "Tables", link: "/table" },
936
+ { label: "Views", link: "/viewedit" },
937
+ { label: "Pages", link: "/pageedit" },
938
+ {
939
+ label: "Settings",
940
+ subitems: [
941
+ { label: "About application", link: "/admin" },
942
+ { label: "Modules", link: "/plugins" },
943
+ { label: "Users and security", link: "/useradmin" },
944
+ ],
945
+ },
946
+ {
947
+ label: "User",
948
+ subitems: [
949
+ { label: "User settings", link: "/auth/settings" },
950
+ { label: "Logout", link: "/auth/logout" },
951
+ ],
952
+ },
953
+ ],
954
+ },
955
+ ];
956
+
957
+ const FAKE_LIST_BODY = `
958
+ <section class="page-section pt-2"><div class="container">
959
+ <div class="card p-1 mt-1 mb-3 d-print-none admin-edit-bar"><div class="card-body p-1">
960
+ <i class="fas fa-user-cog me-1"></i>
961
+ <span class="ms-1 me-2 badge bg-secondary">Page</span>
962
+ <span class="copy-to-clipboard">books_with_edit</span>
963
+ <a class="ms-2" href="/pageedit/edit/books_with_edit">Edit&nbsp;<i class="fas fa-edit"></i></a>
964
+ </div></div>
965
+ </div></section>
966
+ <section class="page-section"><div class="container">
967
+ <div class="d-inline" data-sc-embed-viewname="my_bread">
968
+ <div class="container"><nav aria-label="breadcrumb"><ol class="breadcrumb">
969
+ <li class="breadcrumb-item"><a href="/settings">Settings</a></li>
970
+ <li class="breadcrumb-item"><a href="/admin">About application</a></li>
971
+ <li class="breadcrumb-item active" aria-current="page">Site identity</li>
972
+ </ol></nav></div>
973
+ </div>
974
+ </div></section>
975
+ <section class="page-section"><div class="container">
976
+ <div class="d-inline" data-sc-embed-viewname="books">
977
+ <div class="table-responsive"><table class="table table-sm table-valign-middle table-hover">
978
+ <thead><tr>
979
+ <th><span class="link-style">name</span></th>
980
+ <th><span class="link-style">open</span></th>
981
+ <th class="text-align-right"><span class="link-style">pages</span></th>
982
+ </tr></thead>
983
+ <tbody>
984
+ <tr data-row-id="2">
985
+ <td>War and Peace</td>
986
+ <td><i class="fas fa-lg fa-times-circle text-danger"></i></td>
987
+ <td class="text-align-right">1000</td>
988
+ </tr>
989
+ </tbody>
990
+ </table></div>
991
+ </div>
992
+ </div></section>
993
+ <section class="page-section"><div class="container">
994
+ <div class="d-inline" data-sc-embed-viewname="edit_book">
995
+ <form data-viewname="edit_book" action="/view/edit_book" class="form-namespace" method="post">
996
+ <input type="hidden" name="_csrf" value="<csrf-token>">
997
+ <input type="hidden" class="form-control" name="id" value="2">
998
+ <span style="margin-bottom:1.5rem"><div class="row" style="margin-bottom:1.5rem">
999
+ <div class="col-md-2 text-start text-lg-end"><label for="inputname">name</label></div>
1000
+ <div class="col-md-10 text-start"><input type="text" class="form-control" data-fieldname="name" name="name" id="inputname" value="War and Peace"></div>
1001
+ </div></span>
1002
+ <span style="margin-bottom:1.5rem"><div class="row" style="margin-bottom:1.5rem">
1003
+ <div class="col-md-2 text-start text-lg-end"><label for="inputopen">open</label></div>
1004
+ <div class="col-md-10 text-start"><input class="me-2 mt-1" data-fieldname="open" type="checkbox" name="open" id="inputopen"></div>
1005
+ </div></span>
1006
+ <span style="margin-bottom:1.5rem"><div class="row" style="margin-bottom:1.5rem">
1007
+ <div class="col-md-2 text-start text-lg-end"><label for="inputpages">pages</label></div>
1008
+ <div class="col-md-10 text-start"><input type="number" class="form-control" data-fieldname="pages" name="pages" id="inputpages" step="1" value="1000"></div>
1009
+ </div></span>
1010
+ <span style="margin-bottom:1.5rem"><div class="row" style="margin-bottom:1.5rem">
1011
+ <div class="col-2 text-end"></div>
1012
+ <div class="col-10 text-start"><button type="submit" class="btn btn-primary">Save</button></div>
1013
+ </div></span>
1014
+ </form>
1015
+ </div>
1016
+ </div></section>
1017
+ <section class="page-section"><div class="container">
1018
+ <div id="toasts-area" class="toast-container position-fixed top-0 end-0 p-2" style="z-index:9999;" aria-live="polite" aria-atomic="true"></div>
1019
+ </div></section>`;
1020
+
1021
+ const renderStructureSkeleton = (config) =>
1022
+ menuWrap({
1023
+ brand: { name: "Brand" },
1024
+ menu: FAKE_MENU,
1025
+ config,
1026
+ currentUrl: "/",
1027
+ originalUrl: "/",
1028
+ body: '<section class="page-section pt-2"><div class="container"><p><!-- page content --></p></div></section>',
1029
+ req: null,
1030
+ });
1031
+
1032
+ const renderPageSkeleton = (config) =>
1033
+ menuWrap({
1034
+ brand: { name: "Brand" },
1035
+ menu: FAKE_MENU,
1036
+ config,
1037
+ currentUrl: "/",
1038
+ originalUrl: "/",
1039
+ body: FAKE_LIST_BODY,
1040
+ req: null,
1041
+ });
1042
+
931
1043
  module.exports = {
932
1044
  sc_plugin_api_version: 1,
933
1045
  plugin_name: "bootstrap-prompt-theme",
@@ -980,4 +1092,6 @@ module.exports = {
980
1092
  }
981
1093
  },
982
1094
  ready_for_mobile: true,
1095
+ renderStructureSkeleton,
1096
+ renderPageSkeleton,
983
1097
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/bootstrap-prompt-theme",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Bootstrap 5 layout plugin with LLM-generated CSS overlay",
5
5
  "main": "index.js",
6
6
  "scripts": {