lcp 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/.claude/skills/lcp-custom-field/SKILL.md +10 -1
- data/.claude/skills/lcp-custom-field/agents/openai.yaml +4 -0
- data/.claude/skills/lcp-getting-started/SKILL.md +1 -1
- data/.claude/skills/lcp-getting-started/agents/openai.yaml +4 -0
- data/.claude/skills/lcp-host-binding/agents/openai.yaml +4 -0
- data/.claude/skills/lcp-model/agents/openai.yaml +4 -0
- data/.claude/skills/lcp-permissions/agents/openai.yaml +4 -0
- data/.claude/skills/lcp-presenter/agents/openai.yaml +4 -0
- data/.claude/skills/lcp-workflow/SKILL.md +10 -2
- data/.claude/skills/lcp-workflow/agents/openai.yaml +4 -0
- data/CHANGELOG.md +59 -1
- data/app/assets/javascripts/lcp_ruby/dev_toolbar.js +5 -6
- data/app/controllers/concerns/lcp_ruby/zone_resolution.rb +14 -1
- data/app/helpers/lcp_ruby/display/card_helper.rb +71 -16
- data/app/helpers/lcp_ruby/display_helper.rb +30 -3
- data/app/helpers/lcp_ruby/display_template_helper.rb +3 -3
- data/app/helpers/lcp_ruby/layout_helper.rb +49 -8
- data/app/helpers/lcp_ruby/tree_helper.rb +24 -3
- data/app/models/lcp_ruby/user.rb +10 -0
- data/app/views/layouts/lcp_ruby/application.html.erb +42 -20
- data/app/views/layouts/lcp_ruby/auth.html.erb +6 -1
- data/app/views/lcp_ruby/resources/_show_sections.html.erb +24 -17
- data/app/views/lcp_ruby/resources/_table_index.html.erb +12 -17
- data/app/views/lcp_ruby/widgets/_markdown.html.erb +13 -0
- data/app/views/lcp_ruby/widgets/_presenter_zone.html.erb +23 -7
- data/app/views/lcp_ruby/widgets/_rich_text.html.erb +25 -0
- data/docs/README.md +3 -0
- data/docs/feature-catalog.md +5 -2
- data/docs/feature-catalog.yml +89 -9
- data/docs/getting-started.md +38 -22
- data/docs/guides/dashboards.md +44 -1
- data/docs/guides/display-types.md +6 -0
- data/docs/guides/host-application.md +1 -1
- data/docs/guides/windows-setup.md +211 -0
- data/docs/guides/workflow.md +1 -1
- data/docs/reference/asset-pipeline.md +79 -0
- data/docs/reference/pages.md +50 -1
- data/docs/reference/presenters.md +6 -0
- data/docs/reference/workflow.md +2 -2
- data/examples/crm/Gemfile +3 -0
- data/examples/crm/Gemfile.lock +14 -9
- data/examples/crm/app/assets/config/manifest.js +4 -0
- data/examples/hr/Gemfile +3 -0
- data/examples/hr/Gemfile.lock +14 -9
- data/examples/hr/app/assets/config/manifest.js +4 -0
- data/examples/showcase/Gemfile +3 -0
- data/examples/showcase/Gemfile.lock +12 -9
- data/examples/showcase/app/assets/config/manifest.js +4 -0
- data/examples/showcase/config/lcp_ruby/pages/main_dashboard.yml +17 -2
- data/examples/showcase/config/locales/cs.yml +8 -0
- data/examples/showcase/config/locales/en.yml +8 -0
- data/examples/todo/Gemfile +3 -0
- data/examples/todo/Gemfile.lock +14 -9
- data/examples/todo/app/assets/config/manifest.js +4 -0
- data/exe/lcp +1 -1
- data/lib/generators/lcp_ruby/agent_setup_generator.rb +12 -9
- data/lib/generators/lcp_ruby/claude_skills_generator.rb +23 -19
- data/lib/generators/lcp_ruby/install_generator.rb +171 -3
- data/lib/generators/lcp_ruby/templates/agent_setup/agents_md.md +2 -1
- data/lib/generators/lcp_ruby/templates/agent_setup/claude_md.md +3 -2
- data/lib/generators/lcp_ruby/templates/install/menu.yml.tt +4 -0
- data/lib/generators/lcp_ruby/templates/monitoring/page.yml +5 -0
- data/lib/lcp_ruby/app_template.rb +37 -1
- data/lib/lcp_ruby/array_query.rb +33 -10
- data/lib/lcp_ruby/cli/new_command.rb +9 -4
- data/lib/lcp_ruby/cli/run_command.rb +98 -11
- data/lib/lcp_ruby/cli/skills_command.rb +27 -15
- data/lib/lcp_ruby/cli.rb +5 -1
- data/lib/lcp_ruby/configuration.rb +13 -2
- data/lib/lcp_ruby/custom_fields/applicator.rb +8 -0
- data/lib/lcp_ruby/custom_fields/query.rb +28 -20
- data/lib/lcp_ruby/display/markdown_sanitize.rb +36 -0
- data/lib/lcp_ruby/display/renderers/markdown.rb +6 -13
- data/lib/lcp_ruby/engine.rb +185 -1
- data/lib/lcp_ruby/export/data_generator.rb +5 -1
- data/lib/lcp_ruby/export/value_formatter.rb +34 -0
- data/lib/lcp_ruby/grouped_query/builder.rb +21 -0
- data/lib/lcp_ruby/metadata/configuration_validator.rb +5 -5
- data/lib/lcp_ruby/metadata/field_path.rb +57 -0
- data/lib/lcp_ruby/metadata/loader.rb +33 -1
- data/lib/lcp_ruby/metadata/menu_definition.rb +32 -2
- data/lib/lcp_ruby/metadata/menu_item.rb +53 -4
- data/lib/lcp_ruby/metadata/model_definition.rb +9 -0
- data/lib/lcp_ruby/metadata/zone_definition.rb +12 -5
- data/lib/lcp_ruby/metrics/json_query.rb +5 -0
- data/lib/lcp_ruby/model_factory/builder.rb +5 -0
- data/lib/lcp_ruby/model_factory/json_type_applicator.rb +35 -0
- data/lib/lcp_ruby/model_factory/label_method_builder.rb +3 -19
- data/lib/lcp_ruby/model_factory/schema_manager.rb +19 -0
- data/lib/lcp_ruby/pages/definition_validator.rb +13 -0
- data/lib/lcp_ruby/presenter/field_value_resolver.rb +36 -19
- data/lib/lcp_ruby/schemas/page.json +33 -1
- data/lib/lcp_ruby/search/custom_field_filter.rb +5 -0
- data/lib/lcp_ruby/skills_installer.rb +15 -2
- data/lib/lcp_ruby/tasks/doctor.rb +13 -8
- data/lib/lcp_ruby/version.rb +1 -1
- data/lib/lcp_ruby/widgets/data_resolver.rb +42 -1
- data/lib/lcp_ruby/widgets/date_grouper.rb +19 -0
- data/lib/lcp_ruby/workflow/contract_validator.rb +1 -1
- data/lib/lcp_ruby/workflow/model_source.rb +2 -2
- data/lib/lcp_ruby.rb +18 -0
- data/lib/tasks/lcp_ruby_db.rake +8 -1
- data/lib/tasks/lcp_ruby_i18n_check.rake +8 -0
- metadata +34 -24
- data/examples/crm/erd.md +0 -163
- data/examples/hr/erd.md +0 -396
- data/examples/showcase/erd.md +0 -567
- data/examples/todo/erd.md +0 -21
data/docs/getting-started.md
CHANGED
|
@@ -8,12 +8,16 @@ All examples use the **Ruby DSL** as the primary format. YAML equivalents are pr
|
|
|
8
8
|
|
|
9
9
|
| Requirement | Version |
|
|
10
10
|
|-------------|---------|
|
|
11
|
-
| Ruby | >= 3.
|
|
11
|
+
| Ruby | >= 3.2 |
|
|
12
12
|
| Rails | >= 8.1 (for `lcp new`) or >= 7.1 (manual setup) |
|
|
13
13
|
| Database | SQLite (development), PostgreSQL (production recommended) |
|
|
14
14
|
|
|
15
15
|
LCP Ruby works with any Rails-supported database. SQLite is fine for development and this tutorial.
|
|
16
16
|
|
|
17
|
+
> **On Windows?** A clean Windows box needs a working native-compilation
|
|
18
|
+
> toolchain before `gem install lcp` and `lcp run` succeed. See the
|
|
19
|
+
> [Windows Setup guide](guides/windows-setup.md) for the seven-step path.
|
|
20
|
+
|
|
17
21
|
## Quick Start with `lcp new`
|
|
18
22
|
|
|
19
23
|
The fastest way to create a new LCP Ruby application:
|
|
@@ -30,7 +34,7 @@ This launches an interactive wizard that asks about:
|
|
|
30
34
|
Feature presets:
|
|
31
35
|
| Preset | Includes |
|
|
32
36
|
|--------|----------|
|
|
33
|
-
| **minimal** | Core platform only (sample model + presenter + permissions) |
|
|
37
|
+
| **minimal** | Core platform only (sample `item` model + presenter + permissions) |
|
|
34
38
|
| **standard** | Core + authentication + auditing + saved filters + export + import |
|
|
35
39
|
| **full** | All 17 feature generators |
|
|
36
40
|
| **custom** | Interactive selection of individual features |
|
|
@@ -44,6 +48,14 @@ rails s
|
|
|
44
48
|
|
|
45
49
|
Visit `http://localhost:3000/items` to see the sample application.
|
|
46
50
|
|
|
51
|
+
The sample `item` entity is generated by default so a fresh install has something to click. Each generated file (`models/item`, `presenters/items`, `permissions/item`, `views/items`) carries a `DELETE ME` header pointing at all four siblings; delete them once you've added your own entities via `rails g lcp_ruby:entity`. To skip the sample entirely:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
lcp new my_app --skip-sample
|
|
55
|
+
# or in an existing Rails app:
|
|
56
|
+
rails generate lcp_ruby:install --skip-sample
|
|
57
|
+
```
|
|
58
|
+
|
|
47
59
|
To skip the interactive wizard and use defaults (sqlite3, DSL, minimal):
|
|
48
60
|
|
|
49
61
|
```bash
|
|
@@ -61,7 +73,7 @@ continue, then run `bundle exec rake lcp_ruby:doctor` from the new
|
|
|
61
73
|
app to see exactly what is broken and how to fix it. See
|
|
62
74
|
[reference/doctor.md](reference/doctor.md) for the full diagnostic.
|
|
63
75
|
|
|
64
|
-
`lcp new` also installs [
|
|
76
|
+
`lcp new` also installs [AI agent skills](#ai-agent-skills-ai-assisted-development) into `.claude/skills/` and `.codex/skills/` automatically — see the dedicated section below.
|
|
65
77
|
|
|
66
78
|
### Installing from a local checkout
|
|
67
79
|
|
|
@@ -145,25 +157,25 @@ missing, the generator aborts up-front with a structured
|
|
|
145
157
|
Run `bundle exec rake lcp_ruby:doctor` to see the full feature
|
|
146
158
|
dependency graph.
|
|
147
159
|
|
|
148
|
-
##
|
|
160
|
+
## AI Agent Skills (AI-Assisted Development)
|
|
149
161
|
|
|
150
|
-
LCP Ruby ships a set of
|
|
162
|
+
LCP Ruby ships a set of AI authoring skills (`lcp-getting-started`, `lcp-model`, `lcp-presenter`, `lcp-permissions`, `lcp-workflow`, `lcp-custom-field`, `lcp-host-binding`) that auto-trigger in Claude Code and Codex when you're authoring the corresponding YAML/DSL configuration. Each skill briefs the agent on the 80/20 syntax + decision points + pitfalls and points at the relevant reference doc for the rest. The result: less time grepping for "how do I declare a `belongs_to` with a business-key join?" and fewer round-trips on common authoring tasks.
|
|
151
163
|
|
|
152
|
-
**Auto-installed by `lcp new`** — you don't need to do anything. The skills land in `.claude/skills/` of the new app. Open the project in Claude Code
|
|
164
|
+
**Auto-installed by `lcp new`** — you don't need to do anything. The skills land in `.claude/skills/` and `.codex/skills/` of the new app. Open the project in Claude Code or Codex and skills activate automatically based on context.
|
|
153
165
|
|
|
154
166
|
**Manual install / refresh after gem upgrade:**
|
|
155
167
|
|
|
156
168
|
```bash
|
|
157
|
-
rails generate lcp_ruby:
|
|
169
|
+
rails generate lcp_ruby:agent_setup
|
|
158
170
|
```
|
|
159
171
|
|
|
160
|
-
This copies `.claude/skills/lcp-*/` from the gem path into
|
|
172
|
+
This copies `.claude/skills/lcp-*/` from the gem path into both `.claude/skills/` and `.codex/skills/`, overwriting existing `lcp-*` copies and pruning removed ones. Re-run after `bundle update lcp_ruby` to pick up skills updated for the new gem version — keeps agent instructions in sync with the DSL/schema changes you just bundled.
|
|
161
173
|
|
|
162
|
-
**Verify**: list installed skills via `/skills` inside Claude Code, or
|
|
174
|
+
**Verify**: list installed skills via `/skills` inside Claude Code, or inspect `.claude/skills/` and `.codex/skills/` on disk.
|
|
163
175
|
|
|
164
|
-
**For host-app developers**: commit `.claude/skills/lcp-*` to git so the whole team benefits. (`.claude/settings.local.json` is per-user and stays gitignored.)
|
|
176
|
+
**For host-app developers**: commit `.claude/skills/lcp-*` and `.codex/skills/lcp-*` to git so the whole team benefits. (`.claude/settings.local.json` is per-user and stays gitignored.)
|
|
165
177
|
|
|
166
|
-
**For platform contributors** (working on `lcp_ruby` itself): the canonical skill files live in `.claude/skills/` of the gem repo
|
|
178
|
+
**For platform contributors** (working on `lcp_ruby` itself): the canonical skill files live in `.claude/skills/` of the gem repo; `.codex/skills/lcp-*` is a symlink mirror for Codex. The generator copies the canonical files — single source of truth.
|
|
167
179
|
|
|
168
180
|
## Installation (step-by-step)
|
|
169
181
|
|
|
@@ -175,24 +187,28 @@ Add LCP Ruby to your Gemfile:
|
|
|
175
187
|
gem "lcp"
|
|
176
188
|
```
|
|
177
189
|
|
|
178
|
-
### Asset Pipeline (
|
|
190
|
+
### Asset Pipeline (handled automatically)
|
|
179
191
|
|
|
180
|
-
LCP Ruby bundles its JavaScript and CSS via Sprockets.
|
|
192
|
+
LCP Ruby bundles its JavaScript and CSS via Sprockets. On Rails 8 (Propshaft default), the engine needs `sprockets-rails` as a compatibility shim — otherwise the engine's JS (Stimulus controllers, row-click, advanced filters, form widgets) silently fails to load.
|
|
181
193
|
|
|
182
|
-
|
|
183
|
-
|
|
194
|
+
**You don't need to do anything**: `lcp new` and `rails generate lcp_ruby:install` both add `sprockets-rails` to your Gemfile and create `app/assets/config/manifest.js` with the right link directives automatically. The engine also has a boot-time check that raises `LcpRuby::AssetPipelineError` with a fix recipe if the shim is missing, so silent failure mode is impossible.
|
|
195
|
+
|
|
196
|
+
If you've wired your own ESM bundler (esbuild, importmap with custom pins, etc.) and don't want the shim, opt out:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
lcp new my_app --skip-asset-pipeline
|
|
200
|
+
# or for an existing app:
|
|
201
|
+
rails generate lcp_ruby:install --skip-asset-pipeline
|
|
184
202
|
```
|
|
185
203
|
|
|
186
|
-
|
|
204
|
+
…and silence the boot check:
|
|
187
205
|
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
//= link lcp_ruby/tom-select.css
|
|
192
|
-
//= link lcp_ruby/tom-select.complete.min.js
|
|
206
|
+
```ruby
|
|
207
|
+
# config/initializers/lcp_ruby.rb
|
|
208
|
+
LcpRuby.configure { |c| c.skip_asset_pipeline_check = true }
|
|
193
209
|
```
|
|
194
210
|
|
|
195
|
-
|
|
211
|
+
See [Asset pipeline reference](reference/asset-pipeline.md) for the full background and the future ESM-native roadmap.
|
|
196
212
|
|
|
197
213
|
Run `bundle install`.
|
|
198
214
|
|
data/docs/guides/dashboards.md
CHANGED
|
@@ -128,7 +128,7 @@ Displays an aggregate value (count, sum, avg, min, max) from a model.
|
|
|
128
128
|
|
|
129
129
|
### Text
|
|
130
130
|
|
|
131
|
-
Displays static i18n content.
|
|
131
|
+
Displays static i18n content as plain text (HTML-escaped).
|
|
132
132
|
|
|
133
133
|
```yaml
|
|
134
134
|
- name: welcome
|
|
@@ -139,6 +139,49 @@ Displays static i18n content.
|
|
|
139
139
|
position: { row: 2, col: 1, width: 12, height: 1 }
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
+
For formatted blurbs (headings, links, lists, emphasis) pick `rich_text` or `markdown` instead. All three share the same `content_key:` API.
|
|
143
|
+
|
|
144
|
+
### Rich Text
|
|
145
|
+
|
|
146
|
+
i18n content emitted as raw HTML — locale string can carry `<h2>`, `<a>`, `<strong>`, etc.
|
|
147
|
+
|
|
148
|
+
```yaml
|
|
149
|
+
- name: announcement
|
|
150
|
+
type: widget
|
|
151
|
+
widget:
|
|
152
|
+
type: rich_text
|
|
153
|
+
content_key: lcp_ruby.dashboard.announcement_html # i18n key holds HTML
|
|
154
|
+
position: { row: 2, col: 1, width: 12, height: 1 }
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Trust model: same level as any engine view that uses `raw`. Use when translations live alongside code and go through code review.
|
|
158
|
+
|
|
159
|
+
### Markdown
|
|
160
|
+
|
|
161
|
+
i18n source is parsed as CommonMark (via Commonmarker, with table / tasklist / strikethrough / autolink extensions) and the rendered HTML is sanitized against an explicit tag allow-list. Raw HTML in the markdown source is stripped (`unsafe:` is deliberately omitted from Commonmarker options).
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
- name: welcome
|
|
165
|
+
type: widget
|
|
166
|
+
widget:
|
|
167
|
+
type: markdown
|
|
168
|
+
content_key: lcp_ruby.dashboard.welcome_md # i18n key holds Markdown
|
|
169
|
+
position: { row: 2, col: 1, width: 12, height: 1 }
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
```yaml
|
|
173
|
+
# en.yml
|
|
174
|
+
en:
|
|
175
|
+
lcp_ruby:
|
|
176
|
+
dashboard:
|
|
177
|
+
welcome_md: |
|
|
178
|
+
## Welcome
|
|
179
|
+
|
|
180
|
+
Mix **KPIs**, lists, charts, and text widgets on one page.
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Markdown is the friendlier authoring path for hand-written blurbs and the safer trust posture when translations come from an external pipeline. Mirrors the model-level `:markdown` renderer (`display/renderers/markdown.rb`) so on-page and in-table content render identically.
|
|
184
|
+
|
|
142
185
|
### List
|
|
143
186
|
|
|
144
187
|
Displays a limited list of records from a model.
|
|
@@ -195,6 +195,12 @@ end
|
|
|
195
195
|
|
|
196
196
|
**Appearance:** A rounded pill with a colored background and white or dark text. Values not present in `color_map` fall back to a neutral gray badge.
|
|
197
197
|
|
|
198
|
+
> **`color_map` keys are raw enum keys**, e.g. `draft` / `active` /
|
|
199
|
+
> `paused` — not their humanized labels (`Draft`, `Active`, …) and not
|
|
200
|
+
> localized translations. The badge's display text is humanized
|
|
201
|
+
> automatically from the enum's `label:` / i18n entry; `color_map`
|
|
202
|
+
> operates on the raw stored value.
|
|
203
|
+
|
|
198
204
|
---
|
|
199
205
|
|
|
200
206
|
### `workflow_badge`
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Windows Setup
|
|
2
|
+
|
|
3
|
+
LCP Ruby runs on Windows, but a clean Windows box needs a working Ruby
|
|
4
|
+
native-compilation toolchain before `gem install lcp`, `lcp skills install`,
|
|
5
|
+
and `lcp run <example>` succeed. Most of the friction is **environment-level**
|
|
6
|
+
(how RubyInstaller / rbenv-for-windows ship MSYS2), not LCP itself — but it
|
|
7
|
+
trips up new users, so this page documents the known-good path end to end.
|
|
8
|
+
|
|
9
|
+
> **Reality check.** On a clean Windows 11 box, budget ~1 hour the first time,
|
|
10
|
+
> mostly toolchain troubleshooting. Once the toolchain is fixed, it stays
|
|
11
|
+
> fixed — subsequent `lcp new` / `lcp run` runs are normal.
|
|
12
|
+
|
|
13
|
+
This recipe was field-verified on:
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
|---|---|
|
|
17
|
+
| OS | Windows 11 Enterprise (26200) |
|
|
18
|
+
| Ruby | 3.3.5 (RubyInstaller via rbenv-for-windows) |
|
|
19
|
+
| Shell | PowerShell 5.1, non-elevated |
|
|
20
|
+
|
|
21
|
+
Other Ruby/RubyInstaller versions follow the same shape; exact paths and which
|
|
22
|
+
MSYS2 libs you need may differ.
|
|
23
|
+
|
|
24
|
+
## TL;DR — the seven steps
|
|
25
|
+
|
|
26
|
+
1. **Install rbenv-for-windows + Ruby 3.3.5.**
|
|
27
|
+
|
|
28
|
+
```powershell
|
|
29
|
+
iwr -useb https://github.com/RubyMetric/rbenv-for-windows/raw/main/tool/install.ps1 | iex
|
|
30
|
+
rbenv install 3.3.5
|
|
31
|
+
rbenv global 3.3.5
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
2. **Fix PATH and environment** (user-level env vars). Add to user `PATH`:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
C:\Ruby-on-Windows\msys64\ucrt64\bin
|
|
38
|
+
C:\Ruby-on-Windows\rbenv\bin
|
|
39
|
+
C:\Ruby-on-Windows\shims
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
and set `RBENV_ROOT=C:\Ruby-on-Windows`. (Adjust the drive/folder to wherever
|
|
43
|
+
rbenv-for-windows installed; `RBENV_ROOT` is the root of that tree.) See
|
|
44
|
+
[gotcha C](#c-ucrt64bin-missing-from-path) for why the `ucrt64\bin` entry
|
|
45
|
+
matters.
|
|
46
|
+
|
|
47
|
+
3. **Enable rbenv in your PowerShell profile** (`$PROFILE`):
|
|
48
|
+
|
|
49
|
+
```powershell
|
|
50
|
+
& "$env:RBENV_ROOT\rbenv\bin\rbenv.ps1" init | Out-Null
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
4. **Patch `rbconfig.rb` so native gems compile** — append
|
|
54
|
+
`-Wno-error=incompatible-pointer-types` to `CONFIG["CFLAGS"]`. See
|
|
55
|
+
[gotcha A](#a-gcc-14-treats-incompatible-pointer-types-as-an-error).
|
|
56
|
+
|
|
57
|
+
5. **Overwrite the stale bundled `libwinpthread-1.dll`** with the MSYS2 ucrt64
|
|
58
|
+
copy. See [gotcha D](#d-stale-bundled-libwinpthread-1dll-breaks-require-date).
|
|
59
|
+
|
|
60
|
+
6. **Install the one MSYS2 system library LCP needs that isn't bundled** —
|
|
61
|
+
`libyaml` (required by `psych`). See [gotcha B](#b-msys2-keyring-uninitialized)
|
|
62
|
+
for the keyring workaround.
|
|
63
|
+
|
|
64
|
+
```powershell
|
|
65
|
+
# after the SigLevel workaround in gotcha B:
|
|
66
|
+
ridk exec pacman -Sy --noconfirm
|
|
67
|
+
ridk exec pacman -S --noconfirm mingw-w64-ucrt-x86_64-libyaml
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
7. **Install LCP and run an example.**
|
|
71
|
+
|
|
72
|
+
```powershell
|
|
73
|
+
gem install lcp
|
|
74
|
+
lcp skills install --global
|
|
75
|
+
lcp run showcase
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Environment gotchas (not LCP bugs)
|
|
79
|
+
|
|
80
|
+
These stem from how rbenv-for-windows ships MSYS2 + GCC, not from LCP. They
|
|
81
|
+
affect **every** Ruby project with native-extension gems
|
|
82
|
+
(`websocket-driver`, `bcrypt`, `puma`, `psych`, `io-console`, `stringio`,
|
|
83
|
+
`date`, …) — LCP just happens to depend on several of them.
|
|
84
|
+
|
|
85
|
+
### A. GCC 14+ treats `incompatible-pointer-types` as an error
|
|
86
|
+
|
|
87
|
+
MSYS2's GCC 14 defaults `-Wincompatible-pointer-types` to **error**, which
|
|
88
|
+
breaks the native compile of many common gems on Ruby 3.3.5.
|
|
89
|
+
|
|
90
|
+
A `.gemrc` `:build_args:` workaround fixes `gem install` but **does not
|
|
91
|
+
propagate through Bundler** — so `bundle install` inside an example app still
|
|
92
|
+
fails. The reliable fix is to relax the flag at the compiler-config level:
|
|
93
|
+
|
|
94
|
+
Edit `<RBENV_ROOT>\rubies\<version>\lib\ruby\3.3.0\<arch>\rbconfig.rb` and
|
|
95
|
+
append to `CONFIG["CFLAGS"]`:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
-Wno-error=incompatible-pointer-types
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
> Patch **`CFLAGS`**, not `warnflags` — the Makefile template the gems use
|
|
102
|
+
> doesn't reference `warnflags`, so a change there has no effect.
|
|
103
|
+
|
|
104
|
+
### B. MSYS2 keyring uninitialized
|
|
105
|
+
|
|
106
|
+
After an rbenv-for-windows install, `pacman -S` fails with
|
|
107
|
+
`key "..." is unknown` / `keyring is not writable`, and the usual
|
|
108
|
+
`pacman-key --init && --populate msys2` repair isn't available on the
|
|
109
|
+
`ridk exec` PATH.
|
|
110
|
+
|
|
111
|
+
**Workaround** — temporarily disable signature checking, install, then restore:
|
|
112
|
+
|
|
113
|
+
1. Edit `<RBENV_ROOT>\msys64\etc\pacman.conf` and set:
|
|
114
|
+
|
|
115
|
+
```ini
|
|
116
|
+
SigLevel = Never
|
|
117
|
+
LocalFileSigLevel = Never
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
2. Sync and install the package(s) you need:
|
|
121
|
+
|
|
122
|
+
```powershell
|
|
123
|
+
ridk exec pacman -Sy --noconfirm
|
|
124
|
+
ridk exec pacman -S --noconfirm mingw-w64-ucrt-x86_64-libyaml
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
3. **Restore** the original `SigLevel` / `LocalFileSigLevel` lines in
|
|
128
|
+
`pacman.conf`.
|
|
129
|
+
|
|
130
|
+
`libyaml` is the one library LCP indirectly needs (via `psych`) that MSYS2
|
|
131
|
+
doesn't bundle. Other native gems may pull in other MSYS2 libs — install them
|
|
132
|
+
the same way.
|
|
133
|
+
|
|
134
|
+
### C. `ucrt64\bin` missing from PATH
|
|
135
|
+
|
|
136
|
+
Runtime DLLs (`libgmp`, `libssl`, `libwinpthread`, `libyaml`, …) live in
|
|
137
|
+
`<RBENV_ROOT>\msys64\ucrt64\bin`, but the installer doesn't add it to user
|
|
138
|
+
`PATH`. Without it, even `require 'date'` fails with
|
|
139
|
+
`127: procedure could not be found` once a native extension is built. Adding
|
|
140
|
+
the directory in [step 2](#tldr--the-seven-steps) fixes this.
|
|
141
|
+
|
|
142
|
+
### D. Stale bundled `libwinpthread-1.dll` breaks `require 'date'`
|
|
143
|
+
|
|
144
|
+
The biggest gotcha. The `date` gem's native extension imports
|
|
145
|
+
`clock_gettime64` from `libwinpthread-1.dll`. The copy Ruby ships in
|
|
146
|
+
`<RBENV_ROOT>\rubies\<version>\bin\ruby_builtin_dlls\` is too old — it only
|
|
147
|
+
exports `clock_gettime`, no `_64` variant. Worse, Ruby's `AddDllDirectory`
|
|
148
|
+
loads `ruby_builtin_dlls\` **ahead** of `ucrt64\bin`, so fixing PATH
|
|
149
|
+
([gotcha C](#c-ucrt64bin-missing-from-path)) alone isn't enough — the wrong
|
|
150
|
+
DLL still wins.
|
|
151
|
+
|
|
152
|
+
**Fix** — overwrite the bundled copy with the MSYS2 ucrt64 one:
|
|
153
|
+
|
|
154
|
+
```powershell
|
|
155
|
+
Copy-Item "$env:RBENV_ROOT\msys64\ucrt64\bin\libwinpthread-1.dll" `
|
|
156
|
+
"$env:RBENV_ROOT\rubies\<version>\bin\ruby_builtin_dlls\libwinpthread-1.dll" -Force
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
> This is an upstream packaging mismatch (RubyInstaller ships an older
|
|
160
|
+
> `libwinpthread` than the MSYS2 it bundles); the overwrite is a local
|
|
161
|
+
> workaround until the toolchain ships a matched pair.
|
|
162
|
+
|
|
163
|
+
## LCP-specific notes on Windows
|
|
164
|
+
|
|
165
|
+
The toolchain gotchas above aside, two Windows concerns are handled by LCP
|
|
166
|
+
itself:
|
|
167
|
+
|
|
168
|
+
- **Timezone data is bundled.** Windows has no system `zoneinfo` database, so
|
|
169
|
+
TZInfo needs the pure-Ruby fallback or Rails boot fails with
|
|
170
|
+
`TZInfo::DataSources::ZoneinfoDirectoryNotFound`. The bundled example apps
|
|
171
|
+
(`crm`, `hr`, `showcase`, `todo`) include
|
|
172
|
+
`gem "tzinfo-data", platforms: %i[ windows jruby ]`, and apps generated by
|
|
173
|
+
`lcp new` get it from the Rails 8 generator automatically. No action needed.
|
|
174
|
+
|
|
175
|
+
- **The asset pipeline is wired automatically.** On a Rails 8 (Propshaft-only)
|
|
176
|
+
host, LCP's Sprockets-based JS bundle won't load without a compatibility
|
|
177
|
+
shim. Both `lcp new` and `rails generate lcp_ruby:install` add
|
|
178
|
+
`gem "sprockets-rails"` and create `app/assets/config/manifest.js` by default,
|
|
179
|
+
and the engine fails loudly at boot if the shim is missing — see the
|
|
180
|
+
[Asset Pipeline reference](../reference/asset-pipeline.md). No Windows-specific
|
|
181
|
+
action needed.
|
|
182
|
+
|
|
183
|
+
- **The first-request `Errno::EACCES` is handled.** Sprockets' asset-cache
|
|
184
|
+
write uses a non-atomic `File.rename` that races with Windows file locking,
|
|
185
|
+
and LCP's 12+ precompile entries all hit that cache in parallel on the first
|
|
186
|
+
request — so a fresh `rails server` used to crash its very first page with
|
|
187
|
+
`Permission denied @ rb_file_s_rename` (reload always worked). LCP now swaps
|
|
188
|
+
the file-backed assets cache for a `null_store` on Windows in
|
|
189
|
+
**non-production** (development and test both live-compile assets and race the
|
|
190
|
+
same way; production precompiles at deploy, and Unix is untouched), removing
|
|
191
|
+
the rename race. No action needed; if you ever want the cache back, re-set
|
|
192
|
+
`env.cache` in your own `config.assets.configure` block.
|
|
193
|
+
|
|
194
|
+
## Re-running an example
|
|
195
|
+
|
|
196
|
+
`lcp run <name>` is idempotent: the first run materializes the bundled example
|
|
197
|
+
into a fresh directory and boots it (`bundle install` → `db:setup` → server);
|
|
198
|
+
running the same command again detects the already-materialized directory (via
|
|
199
|
+
the `.lcp-run` marker it dropped after the first successful setup) and just
|
|
200
|
+
re-boots it — `bundle install` → `db:prepare` → server — so your data from the
|
|
201
|
+
previous session survives. (If you dropped the database in the meantime, the
|
|
202
|
+
re-run falls back to `db:setup` and re-seeds.) No flag, no second command:
|
|
203
|
+
|
|
204
|
+
```powershell
|
|
205
|
+
lcp run showcase # first time: materialize + boot
|
|
206
|
+
lcp run showcase # again: re-boot the same app, data preserved
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Pointing `lcp run` at a non-empty directory that LCP did **not** create (no
|
|
210
|
+
`.lcp-run` marker) still aborts with `already exists and is not empty`, so it
|
|
211
|
+
never clobbers your own files. In that case pass a different `DIR`.
|
data/docs/guides/workflow.md
CHANGED
|
@@ -518,7 +518,7 @@ LcpRuby.configure do |config|
|
|
|
518
518
|
end
|
|
519
519
|
```
|
|
520
520
|
|
|
521
|
-
Create the model with required fields (name,
|
|
521
|
+
Create the model with required fields (name, target_model, field_name, states, transitions, version, active). The `states` and `transitions` fields should be JSON columns containing the same structure as the YAML.
|
|
522
522
|
|
|
523
523
|
Static (YAML/DSL) workflows are always loaded if files exist. DB workflows are additive. On name conflicts, static wins.
|
|
524
524
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Asset pipeline
|
|
2
|
+
|
|
3
|
+
LCP Ruby ships its UI as ~50 JavaScript files (45 Stimulus controllers plus helpers, vendored Stimulus 3.2 UMD bundle, and a few utility scripts) and a single 1900-line CSS file. The JS is glued together by Sprockets `//= require` directives in `app/assets/javascripts/lcp_ruby/application.js`.
|
|
4
|
+
|
|
5
|
+
## Why a Sprockets compatibility shim is required
|
|
6
|
+
|
|
7
|
+
Rails 8 ships **Propshaft** as the default asset pipeline. Propshaft is intentionally simple — it serves static assets and does not understand Sprockets' `//= require` directive syntax. Without `sprockets-rails`, two failure modes both produce a silently-broken UI:
|
|
8
|
+
|
|
9
|
+
| Failure mode | Cause | Symptom in browser |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| **A — no sprockets-rails at all** | The `<script>` tag renders (the old `respond_to?(:assets)` gate is true on Propshaft too, so it never actually suppressed it), but Propshaft has no compiled bundle to serve at that logical path | `<script>` tag present, request for LCP JS 404s; Stimulus never loads; top nav stays hidden behind the FOUC mask |
|
|
12
|
+
| **B — sprockets-rails present, manifest.js missing** | Propshaft serves `application.js` as the raw 58-line manifest with `//= require` comments instead of the 11000-line compiled bundle | `<script>` tag present, file fetched 200 OK, body is comments only, no Stimulus loads, top nav stays hidden |
|
|
13
|
+
|
|
14
|
+
Both modes ship no console errors and no server-side warnings. Audit #16 cost ~hours of debugging before identifying the cause — these are the worst kind of failures.
|
|
15
|
+
|
|
16
|
+
## The fix (Phase 1, current)
|
|
17
|
+
|
|
18
|
+
`lcp new` and `rails generate lcp_ruby:install` both:
|
|
19
|
+
|
|
20
|
+
1. Add `gem "sprockets-rails"` to the host's `Gemfile` (idempotent — re-runs don't duplicate)
|
|
21
|
+
2. Create `app/assets/config/manifest.js` with the load-bearing link directives:
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
//= link lcp_ruby/application.js
|
|
25
|
+
//= link lcp_ruby/application.css
|
|
26
|
+
//= link lcp_ruby/tom-select.css
|
|
27
|
+
//= link lcp_ruby/tom-select.complete.min.js
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If the file already exists (e.g. host added their own `Chart.bundle.js` link), only the missing LCP lines are appended — host entries are preserved.
|
|
31
|
+
|
|
32
|
+
The engine adds a **boot-time check** (`lcp_ruby.asset_pipeline_check` initializer → `LcpRuby::Engine.check_asset_pipeline_compat!`) that raises `LcpRuby::AssetPipelineError` if Propshaft is loaded without sprockets-rails. The check skips:
|
|
33
|
+
|
|
34
|
+
- during `rails generate lcp_ruby:install` (chicken-and-egg — the generator is what adds the shim)
|
|
35
|
+
- when `LcpRuby.configuration.skip_asset_pipeline_check = true` (explicit opt-out)
|
|
36
|
+
|
|
37
|
+
So the silent failure mode is impossible — you either get the working UI or a loud actionable error.
|
|
38
|
+
|
|
39
|
+
## Opting out (advanced)
|
|
40
|
+
|
|
41
|
+
If you've wired your own ESM bundler (esbuild, importmap-rails with custom pins, etc.) and serve the engine's JS your own way:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Skip the shim during install
|
|
45
|
+
lcp new my_app --skip-asset-pipeline
|
|
46
|
+
# or for an existing app:
|
|
47
|
+
rails generate lcp_ruby:install --skip-asset-pipeline
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Silence the boot check:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
# config/initializers/lcp_ruby.rb
|
|
54
|
+
LcpRuby.configure do |config|
|
|
55
|
+
config.skip_asset_pipeline_check = true
|
|
56
|
+
end
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
You take responsibility for compiling and serving the engine's JS bundle yourself. The 50 controllers register as `window.StimulusApp.register("lcp-foo", class extends Stimulus.Controller {...})` globals; your bundler needs to load them in the right order (Stimulus core first, then `stimulus_bootstrap.js`, then controllers — see `app/assets/javascripts/lcp_ruby/application.js` for the canonical order).
|
|
60
|
+
|
|
61
|
+
## Phase 2 — ESM-native roadmap (not yet built)
|
|
62
|
+
|
|
63
|
+
The current architecture predates Rails 8's pivot to Propshaft. Long-term the engine should ship a pre-bundled ESM artifact that works on Propshaft natively, without the Sprockets shim. Sketch of the plan:
|
|
64
|
+
|
|
65
|
+
1. Convert all 45 Stimulus controllers from `window.StimulusApp.register(...)` globals to ESM `export default class Foo extends Controller {}`
|
|
66
|
+
2. Add a `package.json` + esbuild build step to the engine repo
|
|
67
|
+
3. Commit the pre-bundled output (or distribute via `gemspec.files` so `bundle install` lays it down)
|
|
68
|
+
4. Layout dual-mode: if the pre-bundled file exists, use it; else fall back to the Sprockets path
|
|
69
|
+
|
|
70
|
+
This is a substantial change (~2000 LOC) with breaking-change implications for hosts that have custom Stimulus controllers reading from `window.StimulusApp`. It is intentionally **not** in scope for the current fix — Phase 1 unblocks every new user today without forcing the bigger migration.
|
|
71
|
+
|
|
72
|
+
## Files
|
|
73
|
+
|
|
74
|
+
- `lib/lcp_ruby/engine.rb` — `check_asset_pipeline_compat!` class method + `lcp_ruby.asset_pipeline_check` initializer
|
|
75
|
+
- `lib/generators/lcp_ruby/install_generator.rb` — `install_asset_pipeline_compat` step + `add_sprockets_rails_to_gemfile!` / `ensure_sprockets_manifest!` helpers
|
|
76
|
+
- `lib/lcp_ruby/app_template.rb` — top-level `gem "sprockets-rails"` so `lcp new` bundles it before the install generator boots Rails
|
|
77
|
+
- `lib/lcp_ruby/configuration.rb` — `skip_asset_pipeline_check` reader/writer (default false)
|
|
78
|
+
- `app/views/layouts/lcp_ruby/application.html.erb` (and `auth.html.erb`) — engine asset includes are gated on the `lcp_emit_engine_assets?` helper (= `!skip_asset_pipeline_check`). The old `respond_to?(:assets)` gate was removed; the new gate suppresses the pipeline-dependent tags only when the host opted out (own bundler), so it doesn't 500 on Propshaft
|
|
79
|
+
- `script/smoke_e2e.sh` — CI smoke test that probes `/assets/lcp_ruby/application.js` and verifies it's the compiled bundle, not the manifest stub
|
data/docs/reference/pages.md
CHANGED
|
@@ -172,7 +172,7 @@ Displays an aggregate value (count, sum, avg, min, max) over a model's records,
|
|
|
172
172
|
|
|
173
173
|
### text
|
|
174
174
|
|
|
175
|
-
Displays translated static content.
|
|
175
|
+
Displays translated static content as plain (HTML-escaped) text.
|
|
176
176
|
|
|
177
177
|
| Attribute | Type | Required | Description |
|
|
178
178
|
|-----------|------|----------|-------------|
|
|
@@ -188,6 +188,55 @@ Displays translated static content.
|
|
|
188
188
|
position: { row: 2, col: 1, width: 12, height: 1 }
|
|
189
189
|
```
|
|
190
190
|
|
|
191
|
+
For formatted content use the `rich_text` or `markdown` widget instead — both share the same `content_key:` API and differ only in how the resolved string is rendered.
|
|
192
|
+
|
|
193
|
+
### rich_text
|
|
194
|
+
|
|
195
|
+
Same `content_key:` lookup as `text`, but the resolved string is emitted via `raw` — so the translation can carry `<h2>`, `<a>`, `<strong>`, etc., and they render as tags. Trust model: locale files are author-controlled, the same level as any engine view that uses `raw`. When translations might originate outside code review (external translator pipeline), prefer `markdown` — its sanitize step makes the trust boundary explicit.
|
|
196
|
+
|
|
197
|
+
| Attribute | Type | Required | Description |
|
|
198
|
+
|-----------|------|----------|-------------|
|
|
199
|
+
| `type` | string | yes | Must be `rich_text`. |
|
|
200
|
+
| `content_key` | string | yes | i18n key whose value is HTML. |
|
|
201
|
+
|
|
202
|
+
```yaml
|
|
203
|
+
- name: announcement
|
|
204
|
+
type: widget
|
|
205
|
+
widget:
|
|
206
|
+
type: rich_text
|
|
207
|
+
content_key: lcp_ruby.dashboard.announcement_html
|
|
208
|
+
position: { row: 2, col: 1, width: 12, height: 1 }
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### markdown
|
|
212
|
+
|
|
213
|
+
i18n source is parsed by [Commonmarker](https://github.com/gjtorikian/commonmarker) (CommonMark + GFM tables, tasklists, strikethrough, autolinks) and the rendered HTML is sanitized against the same tag/attribute allow-list as the model-level `:markdown` renderer. `unsafe:` is **deliberately omitted** from the Commonmarker options, so raw HTML embedded in the source is stripped at parse time.
|
|
214
|
+
|
|
215
|
+
| Attribute | Type | Required | Description |
|
|
216
|
+
|-----------|------|----------|-------------|
|
|
217
|
+
| `type` | string | yes | Must be `markdown`. |
|
|
218
|
+
| `content_key` | string | yes | i18n key whose value is Markdown source. |
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
- name: welcome
|
|
222
|
+
type: widget
|
|
223
|
+
widget:
|
|
224
|
+
type: markdown
|
|
225
|
+
content_key: lcp_ruby.dashboard.welcome_md
|
|
226
|
+
position: { row: 2, col: 1, width: 12, height: 1 }
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
```yaml
|
|
230
|
+
# en.yml
|
|
231
|
+
en:
|
|
232
|
+
lcp_ruby:
|
|
233
|
+
dashboard:
|
|
234
|
+
welcome_md: |
|
|
235
|
+
## Welcome
|
|
236
|
+
|
|
237
|
+
Mix **KPIs**, lists, charts, and *text-style* widgets on one page.
|
|
238
|
+
```
|
|
239
|
+
|
|
191
240
|
### list
|
|
192
241
|
|
|
193
242
|
Displays recent records from a model.
|
|
@@ -1242,6 +1242,12 @@ Renders the value as a colored badge. Useful for enum and status fields.
|
|
|
1242
1242
|
|
|
1243
1243
|
Available colors: `green`, `red`, `blue`, `yellow`, `orange`, `purple`, `gray`, `teal`, `cyan`, `pink`.
|
|
1244
1244
|
|
|
1245
|
+
**`color_map` keys are raw enum keys, not humanized labels.** For an enum
|
|
1246
|
+
field with values `active`, `pending`, `suspended`, write
|
|
1247
|
+
`active: green` — not `Active: green` and not the localized label. The
|
|
1248
|
+
displayed text comes from the enum's `label:` / i18n translation
|
|
1249
|
+
automatically; `color_map` operates on the underlying stored value.
|
|
1250
|
+
|
|
1245
1251
|
```yaml
|
|
1246
1252
|
- field: status
|
|
1247
1253
|
renderer: badge
|
data/docs/reference/workflow.md
CHANGED
|
@@ -538,7 +538,7 @@ LcpRuby.configure do |config|
|
|
|
538
538
|
# DB model source
|
|
539
539
|
config.workflow_model = "workflow_definition"
|
|
540
540
|
config.workflow_model_fields = {
|
|
541
|
-
name: "name",
|
|
541
|
+
name: "name", target_model: "target_model", field_name: "field_name",
|
|
542
542
|
states: "states", transitions: "transitions",
|
|
543
543
|
version: "version", active: "active", audit_log: "audit_log"
|
|
544
544
|
}
|
|
@@ -575,7 +575,7 @@ The DB model must have these fields:
|
|
|
575
575
|
| Logical Name | Type | Description |
|
|
576
576
|
|---|---|---|
|
|
577
577
|
| `name` | string | Unique workflow name |
|
|
578
|
-
| `
|
|
578
|
+
| `target_model` | string | Target model name |
|
|
579
579
|
| `field_name` | string | Enum field name |
|
|
580
580
|
| `states` | json | States hash |
|
|
581
581
|
| `transitions` | json | Transitions hash |
|
data/examples/crm/Gemfile
CHANGED
|
@@ -4,6 +4,9 @@ gem "rails", "~> 7.1"
|
|
|
4
4
|
gem "sqlite3", "~> 1.4"
|
|
5
5
|
gem "puma", "~> 6.0"
|
|
6
6
|
gem "sprockets-rails"
|
|
7
|
+
|
|
8
|
+
# Windows has no system zoneinfo database; bundle the pure-Ruby fallback.
|
|
9
|
+
gem "tzinfo-data", platforms: %i[ windows jruby ]
|
|
7
10
|
gem "image_processing", "~> 1.2"
|
|
8
11
|
gem "chartkick"
|
|
9
12
|
gem "lcp", path: "../.."
|