Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
01821be
Added new widget capability to deliver CMS block editing capability i…
Oct 9, 2026
6e3290f
feat(cmsblock): scaffold editable content draft and template selection
digitalrisedorset Oct 9, 2026
b58ab0d
feat(cmsblock): add persistent demo API and deterministic HTML/CSS ge…
digitalrisedorset Oct 9, 2026
a7d3290
feat(cmsblock): wire editor to draft, review and publication API
digitalrisedorset Oct 9, 2026
c22fda2
feat(cmsblock): replace template dropdown with three visual references
digitalrisedorset Oct 9, 2026
d388f3b
feat(cmsblock): map visual references to three responsive generated l…
digitalrisedorset Oct 9, 2026
34f724a
feat(cmsblock): enlarge template inspection and clarify selected refe…
digitalrisedorset Oct 9, 2026
1bad18c
feat(cmsblock): separate saved editorial style from image layout preview
digitalrisedorset Oct 9, 2026
d82358c
ci(cmsblock): enforce bounded function complexity with Lizard
digitalrisedorset Oct 9, 2026
8e94824
refactor(cmsblock): split widget modes to reduce cyclomatic complexity
digitalrisedorset Oct 9, 2026
345e797
refactor(cmsblock): isolate edit action and preview rendering
digitalrisedorset Oct 9, 2026
fb646af
fix(cmsblock): track independent image placement in editor draft
digitalrisedorset Oct 9, 2026
8e13738
fix(cmsblock): generate layouts independently of editorial style
digitalrisedorset Oct 9, 2026
111534e
test(cmsblock): cover persisted layout and independent generated arra…
digitalrisedorset Oct 9, 2026
4b2d966
test(cmsblock): update legacy layout assertions to independent layout…
digitalrisedorset Oct 9, 2026
dc761c5
fix(cmsblock): close CmsBlockRevision interface
digitalrisedorset Oct 9, 2026
adad7e4
build: centralize widget Lizard checks behind mise complexity task
digitalrisedorset Oct 9, 2026
fce2057
refactor(cmsblock): seed typed server scaffold configuration and acce…
digitalrisedorset Oct 9, 2026
416143a
Added initial express server to introduce the cms block API service
Oct 9, 2026
0e1b6f3
ci(cmsblock): validate generated Express scaffold before route migration
digitalrisedorset Oct 9, 2026
766d74d
refactor(cmsblock): port legacy validator and HTML generator to typed…
digitalrisedorset Oct 9, 2026
bc7f3de
refactor(cmsblock): port legacy JSON revision store to typed model
digitalrisedorset Oct 9, 2026
e51fa68
feat(cmsblock): restore legacy JSON endpoints through Express control…
digitalrisedorset Oct 9, 2026
5a2fd3f
test(cmsblock): verify restored Express routes and publishing lifecycle
digitalrisedorset Oct 9, 2026
c18bec1
fix(cmsblock): type legacy JSON store and normalize optional layouts
digitalrisedorset Oct 9, 2026
4d456e6
test(cmsblock): provide route span stub to HTTP lifecycle test
digitalrisedorset Oct 9, 2026
0e7c4ec
chore(cmsblock): prepare first-iteration PR and drop migrated backup
digitalrisedorset Oct 9, 2026
b6b7fd7
chore(cmsblock): remove widget-specific GitHub Actions workflows
digitalrisedorset Oct 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions launcher/scripts/widget-complexity.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail

# Run from the platform root, independently of the caller's working directory.
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)"
widget="${1:-}"

if [[ ! "$widget" =~ ^[a-z][a-z0-9_-]*$ ]]; then
echo "Usage: mise run complexity -- <widget>" >&2
echo "Widget must be a single name such as cmsblock or productgallery." >&2
exit 2
fi

widget_dir="$root/widgets/$widget"
if [[ ! -d "$widget_dir" ]]; then
echo "Unknown widget '$widget': $widget_dir does not exist." >&2
exit 2
fi

targets=()
for directory in src api; do
if [[ -d "$widget_dir/$directory" ]]; then
targets+=("$directory")
fi
done

if [[ ${#targets[@]} -eq 0 ]]; then
echo "Widget '$widget' has neither src nor api to analyze." >&2
exit 2
fi

if ! command -v lizard >/dev/null 2>&1; then
echo "Lizard not found. Run this check through mise to install the pinned tool." >&2
exit 2
fi

cd "$widget_dir"
exec lizard "${targets[@]}" -C 10 -a 5
5 changes: 5 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,8 @@ run = "./launcher/scripts/widgets-deploy.sh"
[tasks."widgets-test"]
description = "Test the ReactEdge deployed widgets"
run = "./launcher/scripts/widgets-test.sh"

[tasks.complexity]
description = "Check Lizard complexity for one widget: mise run complexity -- <widget>"
tools = {uv = "latest", "pipx:lizard" = "1.24.1"}
run = "./launcher/scripts/widget-complexity.sh {{arg(name='widget', required=true)}}"
4 changes: 4 additions & 0 deletions services/cmsblock/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
PORT=4190
FRONTEND_URL=http://localhost:3000
OTEL_HOST=http://localhost:4318
OTEL_SERVICE_NAME=reactedge-cmsblock
3 changes: 3 additions & 0 deletions services/cmsblock/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# CMSBlock drafts and environment secrets are runtime data.
data/
.env
69 changes: 69 additions & 0 deletions services/cmsblock/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# CMSBlock — first iteration

This service was scaffolded from Operational Intelligence's `create_server`
MCP template, like ReactEdge's ListingRecord service. It uses Express,
TypeScript, request/route OpenTelemetry spans and separate access, controller,
routes, and model layers.

This **development-only** iteration supports a single block, `demo`,
with a deterministic generator (not an AI model).

## Start locally

```bash
npm --prefix services/cmsblock ci
npm --prefix services/cmsblock start
```

Default HTTP address: `http://127.0.0.1:4190`.

For local development, start the CMSBlock widget separately:

```bash
npm --prefix widgets/cmsblock dev
```

The widget uses `http://127.0.0.1:4190/cmsblock/blocks/demo` by default.
Set `VITE_CMSBLOCK_URL` to override the service origin.

## API

| Method | Path | Action |
| --- | --- | --- |
| GET | `/cmsblock/status` | Scaffold status |
| GET | `/cmsblock/blocks/demo` | Read saved block |
| PUT | `/cmsblock/blocks/demo` | Save source, style and layout |
| POST | `/cmsblock/blocks/demo/generate` | Generate a revision for review |
| POST | `/cmsblock/blocks/demo/approve` | Publish the pending revision |
| POST | `/cmsblock/blocks/demo/reject` | Discard the pending revision |

Source content remains separate from pending and approved HTML/CSS. New
drafts do not change the approved revision until approval. The JSON store
uses `data/demo.json` by default and is compatible with the old prototype.
The generator supports three editorial styles and three image arrangements.

Set `PORT`, `CMSBLOCK_DATA_DIR`, `FRONTEND_URL` (comma-separated CORS
origins), `OTEL_HOST`, and `OTEL_SERVICE_NAME` as needed.

## Checks

```bash
npm --prefix services/cmsblock run typecheck
npm --prefix services/cmsblock test
mise run complexity -- cmsblock
```

Run these checks locally when changing the service. To verify startup, call
`curl --fail http://127.0.0.1:4190/cmsblock/status` after starting it.

## Limitations

**Do not expose this API to the public internet.** It has no authentication,
authorization or rate limiting. It listens on loopback by default; CORS is
not an authentication mechanism. The deterministic generator converts
supplied HTML to escaped text and retains up to three HTTPS image URLs;
it does not preserve arbitrary HTML or interpret style reference images.
The widget uses sandboxed iframe previews. Published output will require
separate security review and sanitisation before it can be served directly
as SSR markup. Telemetry exporter delivery is not covered by the current
HTTP lifecycle test.
Loading