Repository navigation
Styling compatibility phase 0: preflight contract, staged modes, and the resolved-style IR #455
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a99c037
cc0fab7
0b97ae5
f661794
35e2b2f
10b5030
4915dae
3aa6728
4324206
24fa109
d7c964f
4748e24
844803d
88c1f03
0a5ca92
2be0b18
a326726
c7c47ff
6c07c88
6deeebc
eb28021
43d98c7
a7865c8
b140f80
3f43411
31e5f44
800a80f
f6b4798
00c0854
34a05dc
b01de25
012d837
d4b9ea7
a228a65
77a2d9d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,3 +1,11 @@ | ||||||
| # Workspace: the core stays the root package; `cascade` is the optional | ||||||
| # style-cascade extension (its own cdylib, loaded lazily and only when a | ||||||
| # native-cascade export is requested — the §33 install budget and the | ||||||
| # import-weight contract are measured with both, but the core never links it). | ||||||
| [workspace] | ||||||
| members = [".", "cascade"] | ||||||
| default-members = [".", "cascade"] | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Building this workspace now compiles the optional Prompt for AI agents
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With Required change: either remove Generated by Claude Code |
||||||
|
|
||||||
| [package] | ||||||
| name = "xy-core" | ||||||
| version = "0.1.0" | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| [package] | ||
| name = "xy-cascade" | ||
| version = "0.1.0" | ||
| edition = "2021" | ||
| description = "Optional mount-free CSS cascade for xy: Lightning CSS parsing plus a profile-scoped resolver over the synthetic chart DOM" | ||
| license = "Apache-2.0" | ||
|
|
||
| [lib] | ||
| name = "xy_cascade" | ||
| crate-type = ["cdylib"] | ||
|
|
||
| # Lightning CSS does the real parsing/normalization; the matcher/cascade over | ||
| # the synthetic slot DOM is profile-scoped and lives here. Everything outside | ||
| # the published profile is reported, never guessed (§28). Pinned exactly: | ||
| # the resolver's output is part of a deterministic export path. | ||
| [dependencies] | ||
| lightningcss = "=1.0.0-alpha.72" | ||
| parcel_selectors = "=0.28.3" | ||
| serde = { version = "1", features = ["derive"] } | ||
| serde_json = "1" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| //! Mount-free CSS cascade for xy (wire-protocol §8, migration doc "native | ||
| //! cascade"): Lightning CSS parses and normalizes author stylesheets; this | ||
| //! crate matches them against the synthetic chart DOM and cascades to | ||
| //! concrete per-node declarations — the values the ResolvedStyleSnapshot | ||
| //! carries and the native writers consume. | ||
| //! | ||
| //! Profile-scoped on purpose. The published profile is: style rules; | ||
| //! class / `[data-xy-slot=…]` attribute / universal / `:root` simple | ||
| //! selectors with descendant and child combinators; `@media | ||
| //! (prefers-color-scheme: …)`; custom properties with `var()` fallbacks; | ||
| //! `em`/`rem` lengths (resolved against the font-size cascade); the | ||
| //! inherited text properties. Everything else — other at-rules, pseudo | ||
| //! classes/elements, sibling combinators, percentage lengths — lands in the | ||
| //! reply's `unsupported` list with the reason, never in a guessed value | ||
| //! (§28). The Python side surfaces that list through the preflight. | ||
| //! | ||
| //! C ABI: JSON in, JSON out, one free function. The boundary stays this | ||
| //! narrow so the extension is loadable lazily and replaceable wholesale | ||
| //! (the plan's Stylo endgame swaps the resolver, not the contract). | ||
|
|
||
| mod resolve; | ||
|
|
||
| use std::panic::{catch_unwind, AssertUnwindSafe}; | ||
|
|
||
| pub const CASCADE_ABI_VERSION: u32 = 1; | ||
|
|
||
| #[no_mangle] | ||
| pub extern "C" fn xy_cascade_abi_version() -> u32 { | ||
| CASCADE_ABI_VERSION | ||
| } | ||
|
|
||
| /// Resolve `css` against the synthetic document `doc` (JSON; see | ||
| /// `resolve::Document`). Writes a malloc'd JSON reply to `out`/`out_len`; | ||
| /// returns 0 on success, 1 when the reply is `{"error": …}`, 2 on a caught | ||
| /// panic (reply is a static error JSON). The caller frees the reply with | ||
| /// `xy_cascade_free` in every case. | ||
| /// | ||
| /// # Safety | ||
| /// `css_ptr`/`doc_ptr` must reference `css_len`/`doc_len` readable bytes; | ||
| /// `out`/`out_len` must be writable. | ||
| #[no_mangle] | ||
| pub unsafe extern "C" fn xy_cascade_resolve( | ||
| css_ptr: *const u8, | ||
| css_len: usize, | ||
| doc_ptr: *const u8, | ||
| doc_len: usize, | ||
| out: *mut *mut u8, | ||
| out_len: *mut usize, | ||
| ) -> i32 { | ||
| let css = std::slice::from_raw_parts(css_ptr, css_len); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Invalid FFI arguments can crash the process instead of returning the documented JSON error reply. Prompt for AI agents |
||
| let doc = std::slice::from_raw_parts(doc_ptr, doc_len); | ||
| let result = catch_unwind(AssertUnwindSafe(|| resolve::resolve_json(css, doc))); | ||
| let (code, payload) = match result { | ||
| Ok(Ok(json)) => (0, json), | ||
| Ok(Err(message)) => ( | ||
| 1, | ||
| serde_json::to_string(&serde_json::json!({ "error": message })) | ||
| .unwrap_or_else(|_| "{\"error\":\"unserializable error\"}".to_string()), | ||
| ), | ||
| Err(_) => (2, "{\"error\":\"panic in xy_cascade_resolve\"}".to_string()), | ||
| }; | ||
| let boxed = payload.into_bytes().into_boxed_slice(); | ||
| let len = boxed.len(); | ||
| *out = Box::into_raw(boxed) as *mut u8; | ||
| *out_len = len; | ||
| code | ||
| } | ||
|
|
||
| /// Free a reply produced by `xy_cascade_resolve`. | ||
| /// | ||
| /// # Safety | ||
| /// `ptr`/`len` must be exactly a pair returned through `out`/`out_len`. | ||
| #[no_mangle] | ||
| pub unsafe extern "C" fn xy_cascade_free(ptr: *mut u8, len: usize) { | ||
| if !ptr.is_null() { | ||
| drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr, len))); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Adding this workspace root breaks the standalone
examples/osm/osmium-rscrate. It is not a member and is not excluded, socargo build --releaserun inside it fails with a workspace-membership error. Add it toexcludeso the osm ingest build keeps working.Prompt for AI agents