Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ All historical references to "CFWheels" in this changelog have been preserved fo

### Fixed

- `QueryBuilder.whereIn()` / `whereNotIn()` with an empty array no longer emit malformed SQL (`property IN ()`). Previously, passing an empty list or array to either method produced syntactically invalid SQL that surfaced as a generic JDBC syntax error from the database, with no pointer back to the call site that built the empty collection. `whereIn(prop, [])` now sets an `$alwaysEmpty` flag on the builder so every terminal method (`count`, `findAll`, `findOne`, `first`, `exists`, `updateAll`, `deleteAll`, `findEach`, `findInBatches`) short-circuits to the appropriate zero-row sentinel before going through the finder. `whereNotIn(prop, [])` is a no-op (exclude-none = match-all), so the chain proceeds normally. Matches the user-facing behaviour every mature ORM converged on (Rails, Sequel, Django, Laravel Eloquent: empty `IN` matches no rows, empty `NOT IN` matches every row). The flag-based design avoids a runtime trap from Wheels' WHERE-clause parser (`vendor/wheels/model/sql.cfc` runs a property-extraction regex over every clause it sees — a raw `1 = 0` literal would be parsed as property `1` and trip `Wheels.ColumnNotFound`). Fourteen new specs in `vendor/wheels/tests/specs/model/queryBuilderSpec.cfc` cover empty-array, empty-list, composition with other clauses, the `whereNotIn` mirrors, every patched terminal (`findAll`, `first` / `findOne`, `exists`, `count`, `updateAll`, `deleteAll`, `findEach`, `findInBatches`), and the documented `select()` / `include()` silent-ignore caveat on the short-circuit path. Both copies of the query-builder guide were updated to document the short-circuit in the methods table (#2736)
- `wheels mcp setup` now writes a stdio-based `.opencode.json` instead of one pointing at the deprecated HTTP MCP endpoint. `cli/src/templates/OpenCodeConfig.json` — the file the setup command actually reads from (`setup.cfc:53`) — still carried the pre-4.0 shape: `"url": "http://localhost:{PORT}/wheels/mcp", "type": "remote"`, with `{PORT}` left as an unsubstituted literal string. OpenCode users running `wheels mcp setup` ended up with a config trying to connect to a host called `{PORT}` against an endpoint that emits a deprecation warning on every call. The template now uses the same stdio form already shipped in `tools/build/base/.opencode.json`: `"type": "local", "command": ["wheels", "mcp", "wheels"]`. The companion monorepo reference copy at `app/snippets/OpenCodeConfig.json` (not read by the setup command, but kept in sync for consistency) was updated to match. The CHANGELOG entry from when the stdio shift originally landed claimed all template copies had been updated; this closes the two that were missed (#2735)
- `wheels packages --help` / `wheels packages help` / `wheels packages -h` now emit a module-owned help string that documents `add` as the canonical install verb and explains why typing `install` does not work (LuCLI's built-in extension installer intercepts the literal verb before dispatch reaches the module — same trap that hit `wheels browser install` → `wheels browser setup` in #2345). Previously the auto-introspected help drifted from the real CLI surface, advertising an `install <name> [--force]` row that never actually installed anything (#2713)
- Package manifest field reference in `web/sites/guides/.../packages.mdx` (both v4-0-0 and v4-0-1-snapshot copies) and `CLAUDE.md`: the inter-package dependency field is `requires`, not `dependencies`. The legacy 3.x plugin shape used `dependencies` in `box.json`; the modern `PackageLoader` (`vendor/wheels/ModuleGraph.cfc`) has always read `requires`, plus `replaces` (exclusion / migration path) and `suggests` (soft load-order edge). Copying the old example manifest would have shipped a package that loaded but silently ignored its declared dependencies — no error, no warning, just a missing-dep failure at the first runtime call into the absent dependency. All three docs now use `requires` and the previously undocumented `replaces` / `suggests` fields are covered alongside. Same PR also tightens the guide's description of `wheelsVersion` mismatches: not just "logged" but a hard skip — incompatible packages are excluded from the load order before their CFC is instantiated and recorded in `failedPackages` with the constraint and running version named in the log (#2734)
Expand Down
25 changes: 25 additions & 0 deletions docs/releases/blog-drafts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Blog Drafts

Unpublished blog posts wait here until they're ready to ship. CI does not pick up files in this folder — `web/content/blog/posts/` is the live source. Move a draft into `web/content/blog/posts/` to publish it (CI will deploy on the next push to develop).

Each draft carries a `publishedAt` date in its frontmatter that's the intended publication day. The deploy is still gated by a human moving the file; the date is what shows on the published article.

## Current queue

Scheduled for every-other-day cadence after the rate-limited API post (published 2026-05-15):

| Draft | Slot |
|---|---|
| `anatomy-of-a-wheels-package.md` | 2026-05-17 |
| `wheels-claude-stdio-mcp.md` | 2026-05-19 |
| `beyond-findall-scopes-enums-query-builder.md` | 2026-05-21 |

The companion social-post skeletons live in `../blog-skeletons/`. When you promote a draft, copy the social skeleton too.

## Publishing checklist

1. Review the draft for any references that need a final pass (cross-links to other posts in the series, date math in teaser lines).
2. `git mv docs/releases/blog-drafts/<post>.md web/content/blog/posts/<post>.md`
3. Commit on a feature branch, open a PR.
4. After the PR merges, the deploy workflow picks up the new file and ships to https://blog.wheels.dev.
5. Post the companion social skeleton(s) on the channels in their checklist.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
title: Anatomy of a Wheels Package
slug: anatomy-of-a-wheels-package
publishedAt: '2026-05-22T14:00:00.000Z'
updatedAt: '2026-05-22T14:00:00.000Z'
publishedAt: '2026-05-17T14:00:00.000Z'
updatedAt: '2026-05-17T14:00:00.000Z'
author: Peter Amiri
tags:
- wheels-4
Expand Down Expand Up @@ -293,4 +293,4 @@ The second was the `wheelsVersion` constraint. The guide described mismatches as

Neither of these is a code change — both are documentation fixes — but they're the kind of drift that costs an hour the first time you hit it, and they're the reason a piece like this is worth writing. Anything you have to write down to be sure of is something the next person was going to have to figure out from scratch.

The next post in the series — *Wheels + Claude: building a feature via the stdio MCP* — picks up the same theme on a different surface: what the framework's tools look like when the consumer is a model rather than a developer. Coming next week.
The next post in the series — *Wheels + Claude: building a feature via the stdio MCP* — picks up the same theme on a different surface: what the framework's tools look like when the consumer is a model rather than a developer. Coming Tuesday.
Loading
Loading