Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
run: |
make clean
make assets
git diff --exit-code static/sf/sf.css static/sf/sf.js
git diff --exit-code static/sf/sf.css static/sf/sf.js static/sf/sf.*.css static/sf/sf.*.js

- name: Check formatting
run: cargo fmt --all -- --check
Expand Down
5 changes: 4 additions & 1 deletion .versionrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@
"filename": "Cargo.toml",
"updater": "scripts/cargo-version.js"
}
]
],
"scripts": {
"postbump": "make clean assets && git add -A static/sf"
}
}
16 changes: 10 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ RUST_VERSION := 1.75+
# ============== Asset Sources ==============
CSS_SRC := $(sort $(wildcard css-src/*.css))
JS_SRC := $(sort $(wildcard js-src/*.js))
VERSIONED_CSS := static/sf/sf.$(VERSION).css
VERSIONED_JS := static/sf/sf.$(VERSION).js

# ============== Phony Targets ==============
.PHONY: banner help assets build build-release test test-quick test-doc test-unit test-one \
Expand All @@ -40,16 +42,18 @@ banner:

# ============== Asset Targets ==============

assets: static/sf/sf.css static/sf/sf.js
assets: static/sf/sf.css static/sf/sf.js $(VERSIONED_CSS) $(VERSIONED_JS)

static/sf/sf.css: $(CSS_SRC)
static/sf/sf.css $(VERSIONED_CSS): $(CSS_SRC)
@printf "$(PROGRESS) CSS sf.css ($(words $(CSS_SRC)) files)\n"
@cat $(CSS_SRC) > $@
@cat $(CSS_SRC) > static/sf/sf.css
@cp static/sf/sf.css $(VERSIONED_CSS)
Comment on lines +47 to +50
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Regenerate sf.$(VERSION) bundles in the release path

These rules make the emitted filenames depend on $(VERSION), but the existing release flow does not rebuild them: .versionrc.json only bumps Cargo.toml, and publish still runs cargo publish without depending on assets. In a 0.1.0 -> 0.1.1 release, that means the crate can be published with only sf.0.1.0.{css,js} present, so the newly documented /sf/sf.0.1.1.css and /sf/sf.0.1.1.js URLs will 404 unless the maintainer remembers to run make assets manually.

Useful? React with 👍 / 👎.

@printf "$(GREEN)$(CHECK) CSS bundled$(RESET)\n"

static/sf/sf.js: $(JS_SRC)
static/sf/sf.js $(VERSIONED_JS): $(JS_SRC)
@printf "$(PROGRESS) JS sf.js ($(words $(JS_SRC)) files)\n"
@cat $(JS_SRC) > $@
@cat $(JS_SRC) > static/sf/sf.js
@cp static/sf/sf.js $(VERSIONED_JS)
@printf "$(GREEN)$(CHECK) JS bundled$(RESET)\n"

# ============== Build Targets ==============
Expand Down Expand Up @@ -238,7 +242,7 @@ publish: banner
clean:
@printf "$(ARROW) Cleaning build artifacts...\n"
@cargo clean
@rm -f static/sf/sf.css static/sf/sf.js
@rm -f static/sf/sf.css static/sf/sf.js static/sf/sf.*.css static/sf/sf.*.js
@printf "$(GREEN)$(CHECK) Clean complete$(RESET)\n"

# ============== Development ==============
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ This repository keeps both shipped UI code and design exploration in the same tr
- Planned or exploratory ideas may appear in CSS or wireframes before the public API is finished. Those should not be treated as supported integration surface until they are wired into a shipped asset and described in the README API reference.
- When adding new surface area, update the JavaScript API, README, and runnable examples in the same change so the public contract stays explicit.

For production caching, versioned bundle filenames are also emitted as
`/sf/sf.<crate-version>.css` and `/sf/sf.<crate-version>.js`. Those versioned
files are served with immutable caching, while the stable `sf.css` and `sf.js`
paths remain available for compatibility.

## Screenshots

**Planner123** — Gantt chart with split panes, project-colored bars, and constraint scoring:
Expand Down Expand Up @@ -506,6 +511,10 @@ Use `make package-verify` to inspect the exact crate contents that would be publ

The verification step checks that required bundled assets and crate metadata are present, and that development-only sources such as `css-src/`, `js-src/`, `scripts/`, and screenshots are not shipped in the published crate.

Bundling writes both stable compatibility assets (`static/sf/sf.css`,
`static/sf/sf.js`) and versioned assets (`static/sf/sf.<version>.css`,
`static/sf/sf.<version>.js`).

## Acknowledgments

solverforge-ui builds on these excellent open-source projects:
Expand Down
44 changes: 43 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,27 @@ fn mime_from_path(path: &str) -> &'static str {
}

fn is_immutable(path: &str) -> bool {
path.starts_with("fonts/") || path.starts_with("vendor/") || path.starts_with("img/")
path.starts_with("fonts/")
|| path.starts_with("vendor/")
|| path.starts_with("img/")
|| is_versioned_bundle(path)
}

fn is_versioned_bundle(path: &str) -> bool {
path.strip_prefix("sf.")
.and_then(|rest| rest.rsplit_once('.'))
.map(|(version, ext)| {
!version.is_empty()
&& version.chars().all(|ch| {
ch.is_ascii_digit()
|| ch == '.'
|| ch == '-'
|| ch == '+'
|| ch.is_ascii_alphabetic()
})
Comment on lines +67 to +73
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept + in versioned bundle names for valid Cargo versions

Cargo accepts package versions with build metadata such as 0.1.0+foo, so the new Makefile can emit sf.0.1.0+foo.css/js. This predicate rejects +, which means those valid versioned bundles fall back to the non-immutable max-age=3600 cache policy and the new caching behavior silently stops working for that class of releases.

Useful? React with 👍 / 👎.

&& matches!(ext, "css" | "js")
})
.unwrap_or(false)
}

#[cfg(test)]
Expand All @@ -65,6 +85,17 @@ mod tests {
};
use tower::util::ServiceExt;

#[test]
fn versioned_bundles_are_detected() {
assert!(is_versioned_bundle("sf.0.1.0.css"));
assert!(is_versioned_bundle("sf.0.1.0.js"));
assert!(is_versioned_bundle("sf.0.2.0-beta.1.js"));
assert!(is_versioned_bundle("sf.0.1.0+build.7.css"));
assert!(!is_versioned_bundle("sf.css"));
assert!(!is_versioned_bundle("sf.js"));
assert!(!is_versioned_bundle("vendor/sf.0.1.0.js"));
}

#[test]
fn caches_paths_are_predicted_correctly() {
assert_eq!(mime_from_path("styles/sf.css"), "text/css; charset=utf-8");
Expand All @@ -78,9 +109,20 @@ mod tests {
assert!(is_immutable("fonts/jetbrains-mono.woff2"));
assert!(is_immutable("vendor/leaflet/leaflet.js"));
assert!(is_immutable("img/solverforge-logo.svg"));
assert!(is_immutable("sf.0.1.0.css"));
assert!(is_immutable("sf.0.1.0+build.7.js"));
assert!(!is_immutable("sf.css"));
}

#[test]
fn mime_detection_still_works_for_versioned_assets() {
assert_eq!(mime_from_path("sf.0.1.0.css"), "text/css; charset=utf-8");
assert_eq!(
mime_from_path("sf.0.1.0+build.7.js"),
"application/javascript; charset=utf-8"
);
}

#[tokio::test]
async fn serves_assets_with_expected_headers() {
let app = routes();
Expand Down
Loading
Loading