Skip to content

Commit f6c2fbc

Browse files
bpamiriclaudegithub-actions[bot]
authored
fix(view): inline Semantic UI icon font so dev page icons render (#2563)
* fix(view): inline Semantic UI icon font so dev page icons render The framework dev pages (/wheels/guides, /wheels/info, /wheels/migrator, /wheels/packages, etc.) inline semantic.min.css into a <style> block. Its @font-face declarations point at relative URLs like themes/default/assets/fonts/icons.woff2, which resolve against the page URL (e.g. /wheels/guides) — there is no static route to serve them, so every <i class="...icon"> rendered as an empty bordered box. Read icons.woff2 once at application scope, base64-encode it, and emit a data-URI @font-face override after the inlined Semantic CSS. Later @font-face for the same font-family wins in the CSS cascade, so this overrides the broken declarations without modifying semantic.min.css. Added in both _header.cfm and _header_simple.cfm (the latter is used by error pages and may render before any "full" page has visited). Closes #2421. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(view): address Reviewer A/B consensus findings (round 1) - Wrap icon-font init in double-checked cflock on both _header.cfm and _header_simple.cfm. Build the data URI into a local variable inside an exclusive lock and assign to application scope only once, eliminating the intermediate empty-string state that let a concurrent request read "" and skip the @font-face block. Mirrors the pattern in BrowserTest.cfc::$ensureLauncher. - Add CHANGELOG [Unreleased] ### Fixed entry referencing #2563. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
1 parent 4fd9bf1 commit f6c2fbc

3 files changed

Lines changed: 70 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ All historical references to "CFWheels" in this changelog have been preserved fo
152152

153153
### Fixed
154154

155+
- Framework dev pages (`/wheels/guides`, `/wheels/info`, `/wheels/migrator`, `/wheels/packages`, error screens) now render Semantic UI icons instead of empty bordered squares. The dev layouts inline `semantic.min.css` into a `<style>` block, so its relative URLs to `themes/default/assets/fonts/icons.woff2` resolved against the page URL and 404'd — every `<i class="...icon">` rendered as the fallback square. `_header.cfm` and `_header_simple.cfm` now read the woff2 once at application scope, base64-encode it, and emit a `@font-face` override after the inlined Semantic CSS. Initialization uses double-checked locking on `application.wheels.iconsFontDataUri` so concurrent first-requests can't read an intermediate empty value. (#2563)
155156
- Debug bar Tools → Packages page now lists packages available from the `wheels-dev/wheels-packages` registry in fresh apps generated with `wheels new`. The previous gate (`FileExists("/cli/lucli/services/packages/Registry.cfc")`) silently returned an empty list because user apps don't ship the CLI alongside the framework. The registry reader now lives at `vendor/wheels/services/packages/{Registry,HttpClient,ManifestCache}.cfc` and ships with every generated app. The registry list stays scoped to the standalone Tools → Packages page; the inline debug-bar Environment panel shows installed packages only, so the bar stays compact and doesn't trigger a registry walk on every dev-mode page load. (#2530)
156157
- `Registry.fetchManifest()` now validates that a manifest contains a non-empty `versions` array before returning, throwing `Wheels.Packages.RegistryMalformed` instead of letting a downstream `local.m.versions[ArrayLen(...)]` access crash with an unhandled `Expression` error. The per-package skip-on-malformed catch in `listAll()` now actually catches every malformed shape, so the Tools → Packages page degrades gracefully when the registry serves a partial manifest. Mirrored into the CLI's `cli/lucli/services/packages/Registry.cfc` to keep both copies in sync. (#2530)
157158
- Snapshot pre-releases on `develop` now publish the full artifact set (`wheels-core-*.zip`, `wheels-base-template-*.zip`, `wheels-cli-*.zip`, `wheels-starter-app-*.zip`) alongside `wheels-module-*`. Previously only the module tarball was attached, which broke Homebrew/Chocolatey distributions that depend on fetching `wheels-core-*.zip` as a companion artifact: users scaffolded a new app and hit "Could not locate the Wheels framework source" at chapter 1 of the tutorial. Snapshots now mirror the main-branch release contents exactly, flagged as pre-release.

vendor/wheels/public/layout/_header.cfm

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,37 @@ if (!IsDefined("pageHeader")) {
77
// Css Path
88
request.wheelsInternalAssetPath = application.wheels.webpath & "wheels/public/assets";
99
10+
// Inline the Semantic UI icon font as a data URI. The framework's dev pages
11+
// inline semantic.min.css into a <style> block, so its relative URLs to
12+
// `themes/default/assets/fonts/icons.woff2` resolve against the page URL
13+
// (e.g. /wheels/guides) and 404 — every <i class="...icon"> renders as an
14+
// empty box. Cached at application scope; falls back silently if the font
15+
// can't be read. See issue ##2421.
16+
// Wrapped in cflock to guard the check-then-act on application scope
17+
// against parallel first-request initialization. Without the lock a
18+
// second thread that wins the StructKeyExists race after the first
19+
// thread's initial assignment but before its file-read completes
20+
// reads an empty data URI and skips the @font-face block — the exact
21+
// symptom this PR is fixing. Same double-checked locking pattern as
22+
// BrowserTest.cfc::$ensureLauncher(). The value is built into a
23+
// local variable and assigned to application scope only once, so
24+
// readers never see an intermediate empty-string state.
25+
if (!StructKeyExists(application.wheels, "iconsFontDataUri")) {
26+
lock name="wheelsIconsFontInit" type="exclusive" timeout="10" {
27+
if (!StructKeyExists(application.wheels, "iconsFontDataUri")) {
28+
local.iconsFontPath = ExpandPath("/wheels/public/assets/css/woff_files/icons.woff2");
29+
local.dataUri = "";
30+
if (FileExists(local.iconsFontPath)) {
31+
try {
32+
local.dataUri = "data:font/woff2;base64," & ToBase64(FileReadBinary(local.iconsFontPath));
33+
} catch (any e) {
34+
}
35+
}
36+
application.wheels.iconsFontDataUri = local.dataUri;
37+
}
38+
}
39+
}
40+
1041
// Opt the request into the dev debug bar emitted at onrequestend.
1142
// Public.cfc handlers <cfinclude> these views directly (bypassing
1243
// renderView, which is what normally flips this flag), so without this
@@ -125,6 +156,15 @@ if (StructKeyExists(url, "refresh")) {
125156
<style>
126157
<cfinclude template="/wheels/public/assets/css/semantic.min.css">
127158
<cfinclude template="/wheels/public/assets/css/highlight_default.min.css">
159+
<cfif Len(application.wheels.iconsFontDataUri)>
160+
@font-face {
161+
font-family: 'Icons';
162+
src: url("#application.wheels.iconsFontDataUri#") format('woff2');
163+
font-weight: normal;
164+
font-style: normal;
165+
font-display: block;
166+
}
167+
</cfif>
128168
.h-100 {height:100%;}
129169
.forcescroll { overflow-y: scroll; max-height: 40rem; }
130170
.margin-top { margin-top: 5em; }

vendor/wheels/public/layout/_header_simple.cfm

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,26 @@ Static simple version of the header/navigation for output on error screens
1010
if (StructKeyExists(request, "wheels") && IsStruct(request.wheels)) {
1111
request.wheels.showDebugInformation = true;
1212
}
13+
14+
// Inline icon font (see _header.cfm and issue ##2421). Duplicated here
15+
// because error pages can render before _header.cfm has been visited.
16+
// Double-checked locking matches _header.cfm — see comment there for
17+
// the TOCTOU rationale.
18+
if (!StructKeyExists(application.wheels, "iconsFontDataUri")) {
19+
lock name="wheelsIconsFontInit" type="exclusive" timeout="10" {
20+
if (!StructKeyExists(application.wheels, "iconsFontDataUri")) {
21+
local.iconsFontPath = ExpandPath("/wheels/public/assets/css/woff_files/icons.woff2");
22+
local.dataUri = "";
23+
if (FileExists(local.iconsFontPath)) {
24+
try {
25+
local.dataUri = "data:font/woff2;base64," & ToBase64(FileReadBinary(local.iconsFontPath));
26+
} catch (any e) {
27+
}
28+
}
29+
application.wheels.iconsFontDataUri = local.dataUri;
30+
}
31+
}
32+
}
1333
</cfscript>
1434
<cfoutput>
1535
<!--- cfformat-ignore-start --->
@@ -21,6 +41,15 @@ if (StructKeyExists(request, "wheels") && IsStruct(request.wheels)) {
2141
<meta name="robots" content="noindex,nofollow">
2242
<style>
2343
<cfinclude template="/wheels/public/assets/css/semantic.min.css">
44+
<cfif Len(application.wheels.iconsFontDataUri)>
45+
@font-face {
46+
font-family: 'Icons';
47+
src: url("#application.wheels.iconsFontDataUri#") format('woff2');
48+
font-weight: normal;
49+
font-style: normal;
50+
font-display: block;
51+
}
52+
</cfif>
2453
/* ===== Wheels Dark Error Theme ===== */
2554
:root {
2655
--w-bg-base: ##1e1e2e;

0 commit comments

Comments
 (0)