desktop grid client
git clone https://git.lucas.co/cce-grid.git
fix: a link card leads its URL with the link glyph, not an emoji
The card's text was "🔗 {url}", an emoji in whatever face the font fell
back to. The URL is laid out alone, a glyph's room narrower, and the card
paints the cce-icons link glyph before it, in the URL's colour on its
first line — as cce-ui's markdown leads an embed it cannot show.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md | 5 ++++-
src/main.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 53 insertions(+), 4 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 6447c8c..4905557 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -71,7 +71,10 @@ no vault, `$XDG_DATA_HOME/cce/desktop.canvas`. Obsidian-on-cce milestone 5.
- **Cards** (note, text, link, file) draw through cce-ui's `MarkdownView`
(`widget::markdown`, the `markdown` feature): laid out in virtual units
(`layout_cards`, cached per node/width/text), painted with
- `paint_scaled(k = patch.scale)`. Two traps, both fixed here:
+ `paint_scaled(k = patch.scale)`. A link card lays out the URL alone,
+ `link_glyph_room` narrower, and `paint_card` leads it with the `link`
+ cce-icons glyph (it was a "🔗 " emoji in the text until 2026-10-05).
+ Two traps, both fixed here:
- **The scale factor.** The runner renders this surface at scale 1 but
the toolkit-wide factor follows the output (2 on the laptop), and text
shapes at it: `display_list` pins it to the surface's own scale, or
diff --git a/src/main.rs b/src/main.rs
index ca07526..5d1dc8a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -741,7 +741,8 @@ impl GridApp {
let s = p.scale;
let mut jobs: Vec<(String, String, f64)> = Vec::new();
for (item, _) in &self.items {
- let width = (item.w - 2.0 * CARD_PAD).max(20.0);
+ let lead = if matches!(item.kind, Kind::Link(_)) { link_glyph_room() as f64 } else { 0.0 };
+ let width = (item.w - 2.0 * CARD_PAD - lead).max(20.0);
let source = match &item.kind {
Kind::Note(path) => {
if self.cards.get(&item.node).is_some_and(|c| (c.width - width).abs() < 0.5) {
@@ -750,7 +751,8 @@ impl GridApp {
std::fs::read_to_string(path).unwrap_or_else(|_| format!("*{} is missing*", item.name()))
}
Kind::Text(t) => t.clone(),
- Kind::Link(u) => format!("🔗 {u}"),
+ // The URL alone; `paint_card` leads it with the `link` glyph.
+ Kind::Link(u) => u.clone(),
Kind::File(_) => format!("**{}**", item.name()),
_ => continue,
};
@@ -796,8 +798,41 @@ impl GridApp {
if let Some(c) = self.cards.get(&item.node) {
let pad = (CARD_PAD * s) as f32;
let inner = Rect { x: r.x + pad * 0.5, y: r.y + pad * 0.5, width: r.width - pad, height: r.height - pad };
+ // A link card's URL is led by the `link` glyph, in the colour
+ // the URL is drawn in, centred on its first line — the way
+ // cce-ui's markdown leads an embed it cannot show inline. The
+ // URL was laid out `link_glyph_room` narrower to make the room.
+ let glyph = matches!(item.kind, Kind::Link(_)).then(|| {
+ let (size, side) = (CARD_TEXT, link_glyph_side());
+ let (y, color) = c
+ .layout
+ .draws
+ .iter()
+ .find_map(|d| match d {
+ markdown::Draw::Text { y, color, .. } => Some((*y, *color)),
+ _ => None,
+ })
+ .unwrap_or((0.0, cce_ui::colors::TEXT_FG));
+ // A text draw's colour is linear (it is painted through
+ // `srgb_u8`); a glyph is tinted in sRGB, as a text colour.
+ let color = cce_ui::colors::to_srgb(color);
+ let sf = s as f32;
+ let rect = Rect {
+ x: r.x + pad,
+ y: r.y + pad + (y + 0.5 * (size * 1.3 - side)) * sf,
+ width: side * sf,
+ height: side * sf,
+ };
+ (rect, color)
+ });
+ let lead = if glyph.is_some() { link_glyph_room() * s as f32 } else { 0.0 };
for frag in uncovered(inner, above) {
- pc.clip(frag, |pc| c.layout.paint_scaled(pc, (r.x + pad, r.y + pad), s as f32, frag));
+ pc.clip(frag, |pc| {
+ if let Some((rect, color)) = glyph {
+ pc.icon("link", rect, color);
+ }
+ c.layout.paint_scaled(pc, (r.x + pad + lead, r.y + pad), s as f32, frag)
+ });
}
}
}
@@ -912,6 +947,17 @@ fn anchor(i: &items::DesktopItem, side: Option<&str>, toward: (f64, f64)) -> ((f
}
}
+/// The `link` glyph's side on a link card, in card units (the size cce-ui's
+/// markdown gives the glyph leading an embed).
+fn link_glyph_side() -> f32 {
+ (CARD_TEXT * 0.85).round()
+}
+
+/// What a link card's URL is moved right by: the glyph and a gap.
+fn link_glyph_room() -> f32 {
+ link_glyph_side() + (CARD_TEXT * 0.4).round()
+}
+
fn srgb_u8(linear: [f32; 4]) -> [u8; 3] {
let c = cce_ui::colors::to_srgb(linear);
[(c[0] * 255.0) as u8, (c[1] * 255.0) as u8, (c[2] * 255.0) as u8]