Agent: codex
@@ -22,6 +22,10 @@ bits and missing shared libraries with `ldd`. Existing user data and releases
|
||||
owned by another commit are preserved. Use `--dry-run` to inspect destinations;
|
||||
use `--prefix DIR` for a self-contained staging prefix.
|
||||
|
||||
Include `vernier.svg` from `packaging/appimage/` beside the release binaries to
|
||||
install the app's desktop icon. It is checksummed with the release and follows
|
||||
the same `current` pointer as the executable.
|
||||
|
||||
Before activation, install under a staging prefix and run the installed-worker
|
||||
`file-commands-actual` and `local-linux-release` driven workflows, including
|
||||
save/reopen and STEP/STL export. Then run the staged native launcher with
|
||||
|
||||
|
Before Width: | Height: | Size: 305 KiB After Width: | Height: | Size: 310 KiB |
|
Before Width: | Height: | Size: 303 KiB After Width: | Height: | Size: 308 KiB |
|
Before Width: | Height: | Size: 298 KiB After Width: | Height: | Size: 303 KiB |
|
Before Width: | Height: | Size: 297 KiB After Width: | Height: | Size: 303 KiB |
|
Before Width: | Height: | Size: 288 KiB After Width: | Height: | Size: 292 KiB |
@@ -11,7 +11,7 @@
|
||||
--ink: #16181A; /* text · tiles · outlines oklch(0.21 0.006 250) */
|
||||
--ink-warm: #1B1C19; /* warm ink — headings on paper */
|
||||
--slate: #56564E; /* secondary text oklch(0.45 0.008 95) */
|
||||
--mute: #9A958A; /* tertiary / metadata on paper */
|
||||
--mute: #6F6B62; /* readable tertiary / metadata on paper */
|
||||
|
||||
--paper: #F4F1EA; /* primary background oklch(0.96 0.006 90) */
|
||||
--paper-raised: #FBF9F3; /* raised card surface on paper */
|
||||
@@ -23,7 +23,7 @@
|
||||
--dark-raised: #1C1F22; /* raised row on dark (hover) */
|
||||
--dark-line: #26292C; /* borders on dark */
|
||||
--dark-text: #C9CCC8; /* primary text on dark */
|
||||
--dark-mute: #7C8085; /* secondary text on dark */
|
||||
--dark-mute: #A0A5AC; /* secondary text on dark */
|
||||
--dark-dot: #3A3E43; /* inert terminal dot */
|
||||
|
||||
/* ---- Alternate accents (same role as amber; one at a time) ---- */
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
:root {
|
||||
/* ---- Families ---- */
|
||||
--font-display: 'Silkscreen', 'Courier New', monospace; /* display · logo · labels (bitmap) */
|
||||
--font-display: 'Silkscreen', 'Courier New', monospace; /* brand · headings at 14px and above */
|
||||
--font-mono: 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace; /* code · values · metadata */
|
||||
--font-sans: 'IBM Plex Sans', system-ui, -apple-system, sans-serif; /* body · long-form */
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
--t-display-l: 34px; /* page title */
|
||||
--t-display-m: 22px; /* section / icon tile */
|
||||
--t-display-s: 14px; /* label / principle name */
|
||||
--t-label: 11px; /* eyebrow / kicker (Silkscreen) */
|
||||
--t-label-xs: 8px; /* micro caption */
|
||||
--t-label: 12px; /* eyebrow / kicker (Plex Mono) */
|
||||
--t-label-xs: 12px; /* small caption (Plex Mono) */
|
||||
|
||||
/* ---- Mono scale (IBM Plex Mono) ---- */
|
||||
--t-mono-l: 15px; /* command line */
|
||||
|
||||
@@ -21,6 +21,43 @@
|
||||
use crate::theme::{self, ShellTheme};
|
||||
use egui::{Color32, Rect, Response, Sense, Ui, Vec2, pos2, vec2};
|
||||
|
||||
/// VernierCAD's two-tone V. Coordinates match `packaging/appimage/vernier.svg`.
|
||||
/// The shell draws native geometry so the accent follows Settings and the
|
||||
/// mark stays sharp at every display scale. Tiny tiles omit the scale ticks.
|
||||
pub fn app_logo(ui: &mut Ui, theme: ShellTheme, size: f32) -> Response {
|
||||
let (rect, response) = ui.allocate_exact_size(Vec2::splat(size), Sense::hover());
|
||||
if ui.is_rect_visible(rect) {
|
||||
let painter = ui.painter();
|
||||
painter.rect_filled(rect, size * 40.0 / 256.0, theme::INK);
|
||||
let at = |x: f32, y: f32| rect.min + vec2(x, y) * (size / 256.0);
|
||||
for (points, color) in [
|
||||
(
|
||||
[(28.0, 56.0), (80.0, 56.0), (128.0, 164.0), (128.0, 232.0)],
|
||||
theme::PAPER,
|
||||
),
|
||||
(
|
||||
[(128.0, 164.0), (176.0, 56.0), (228.0, 56.0), (128.0, 232.0)],
|
||||
theme.accent,
|
||||
),
|
||||
] {
|
||||
painter.add(egui::Shape::convex_polygon(
|
||||
points.into_iter().map(|(x, y)| at(x, y)).collect(),
|
||||
color,
|
||||
egui::Stroke::NONE,
|
||||
));
|
||||
}
|
||||
if size >= 48.0 {
|
||||
for y in [32.0, 43.0] {
|
||||
painter.line_segment(
|
||||
[at(212.0, y), at(230.0, y)],
|
||||
egui::Stroke::new(size * 5.0 / 256.0, theme.accent),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
response
|
||||
}
|
||||
|
||||
/// The grid Nib is drawn on. Sixteen rows of sixteen.
|
||||
pub const GRID: usize = 16;
|
||||
|
||||
@@ -657,8 +694,8 @@ fn tile_em(painter: &egui::Painter, size: f32) -> f32 {
|
||||
/// the brackets are wider than they look, and two extra pixels each side is
|
||||
/// what pushed the tile lockup past the 26px square it has to sit inside.
|
||||
fn measure_lockup(painter: &egui::Painter, em: f32, gap: f32, word: &str) -> f32 {
|
||||
let bracket = theme::caption(em * 1.5);
|
||||
let body = theme::caption(em);
|
||||
let bracket = egui::FontId::new(em * 1.5, theme::display());
|
||||
let body = egui::FontId::new(em, theme::display());
|
||||
let w = |font: egui::FontId, text: &str| {
|
||||
painter
|
||||
.layout_no_wrap(text.to_owned(), font, Color32::PLACEHOLDER)
|
||||
@@ -681,8 +718,8 @@ fn paint_lockup(
|
||||
letters: Color32,
|
||||
word: &str,
|
||||
) {
|
||||
let bracket_font = theme::caption(em * 1.5);
|
||||
let body_font = theme::caption(em);
|
||||
let bracket_font = egui::FontId::new(em * 1.5, theme::display());
|
||||
let body_font = egui::FontId::new(em, theme::display());
|
||||
let width = measure_lockup(painter, em, gap, word);
|
||||
let mut x = at.x - width / 2.0;
|
||||
|
||||
@@ -910,6 +947,8 @@ mod tests {
|
||||
let output = ctx.run_ui(egui::RawInput::default(), |ui| {
|
||||
for surface in [Surface::Paper, Surface::Dark] {
|
||||
logo_tile(ui, theme, 26.0, surface);
|
||||
app_logo(ui, theme, 26.0);
|
||||
app_logo(ui, theme, 64.0);
|
||||
logo_wordmark(ui, theme, 26.0, surface);
|
||||
nib(ui, 96.0, surface, theme.accent, true);
|
||||
}
|
||||
|
||||
@@ -344,10 +344,7 @@ pub fn constraint_tile(
|
||||
response.on_hover_text(label)
|
||||
}
|
||||
|
||||
/// A Silkscreen group caption — 8px, mute, wide-tracked.
|
||||
///
|
||||
/// Glance-only by construction: it takes a `&str` and paints one line, so
|
||||
/// nobody can put a paragraph in a face whose `b` and `n` are the same height.
|
||||
/// A small group caption, using the theme's readable caption face and size.
|
||||
pub fn caption(ui: &mut Ui, text: &str) {
|
||||
let galley =
|
||||
ui.painter()
|
||||
|
||||
@@ -4141,7 +4141,8 @@ fn title_bar(
|
||||
brand_tile(ui, theme);
|
||||
ui.label(
|
||||
egui::RichText::new("VernierCAD")
|
||||
.font(theme::caption(13.0))
|
||||
.font(theme::sans(17.0))
|
||||
.strong()
|
||||
.color(theme::INK),
|
||||
);
|
||||
// THE VERSION IS THE FIRST THING TO GO. Everything on this
|
||||
@@ -4464,15 +4465,9 @@ fn command_bar_field(ui: &mut egui::Ui, state: &mut ShellState, theme: ShellThem
|
||||
);
|
||||
}
|
||||
|
||||
/// The `[nb]` tile, from the brand module.
|
||||
///
|
||||
/// **NO LONGER A PLACEHOLDER.** The first pass painted an ink square with "nb"
|
||||
/// on it, on the reading that the handoff carried the mark only as a
|
||||
/// design-tool import. The `_ds_bundle.js` beside those screens carries
|
||||
/// `Logo.jsx` in full — and the mark is live Silkscreen type rather than a
|
||||
/// vector, which is exactly why no SVG of it exists to vendor.
|
||||
/// The VernierCAD app mark; the maker's mark remains in the brand module.
|
||||
fn brand_tile(ui: &mut egui::Ui, theme: ShellTheme) {
|
||||
brand::logo_tile(ui, theme, 26.0, brand::Surface::Paper);
|
||||
brand::app_logo(ui, theme, 26.0);
|
||||
}
|
||||
|
||||
/// A 1px vertical divider in a horizontal row.
|
||||
|
||||
@@ -2264,6 +2264,10 @@ pub(crate) fn status_line(
|
||||
.exact_size(theme::h_status(theme.compact))
|
||||
.frame(frame)
|
||||
.show(ui, |ui| {
|
||||
// This read-only band has no controls. A normal interaction row
|
||||
// would be taller than the band and clip the status text below
|
||||
// the window edge, especially after increasing the font size.
|
||||
ui.spacing_mut().interact_size.y = ui.available_height();
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 12.0;
|
||||
let width = ui.available_width();
|
||||
|
||||
@@ -33,7 +33,7 @@ pub const INK_WARM: Color32 = Color32::from_rgb(0x1B, 0x1C, 0x19);
|
||||
/// `--slate` · secondary text.
|
||||
pub const SLATE: Color32 = Color32::from_rgb(0x56, 0x56, 0x4E);
|
||||
/// `--mute` · tertiary text and metadata.
|
||||
pub const MUTE: Color32 = Color32::from_rgb(0x9A, 0x95, 0x8A);
|
||||
pub const MUTE: Color32 = Color32::from_rgb(0x6F, 0x6B, 0x62);
|
||||
/// `--dark-raised` · raised row on the dark surface (in-viewport chips).
|
||||
pub const DARK_RAISED: Color32 = Color32::from_rgb(0x1C, 0x1F, 0x22);
|
||||
/// `--dark-line` · borders on the dark surface.
|
||||
@@ -41,7 +41,7 @@ pub const DARK_LINE: Color32 = Color32::from_rgb(0x26, 0x29, 0x2C);
|
||||
/// `--dark-text` · primary text on the dark surface.
|
||||
pub const DARK_TEXT: Color32 = Color32::from_rgb(0xC9, 0xCC, 0xC8);
|
||||
/// `--dark-mute` · secondary text on the dark surface.
|
||||
pub const DARK_MUTE: Color32 = Color32::from_rgb(0x7C, 0x80, 0x85);
|
||||
pub const DARK_MUTE: Color32 = Color32::from_rgb(0xA0, 0xA5, 0xAC);
|
||||
|
||||
/// `--amber` · the default accent. The user may set any hue in Settings, so
|
||||
/// read [`ShellTheme::accent`] rather than this constant at call sites.
|
||||
@@ -102,10 +102,8 @@ pub const ROW_ROOMY: f32 = 36.0;
|
||||
/// the only surface that prints a refusal — was cut through the middle and
|
||||
/// the lower half painted below the window's edge (audit R2A-04).
|
||||
///
|
||||
/// The remaining two pixels of descender clip at BOTH densities are the
|
||||
/// known, separately-tracked issue and are deliberately untouched here: the
|
||||
/// band is four pixels short of its row at compact and this keeps it
|
||||
/// exactly four pixels short at roomy.
|
||||
/// The status painter limits its read-only row to the available inner height,
|
||||
/// so neither density clips the text below the window edge.
|
||||
#[must_use]
|
||||
pub const fn h_status(compact: bool) -> f32 {
|
||||
H_STATUS + row_height(compact) - ROW_COMPACT
|
||||
@@ -244,11 +242,9 @@ impl ShellTheme {
|
||||
style.spacing.window_margin = egui::Margin::same(16);
|
||||
|
||||
style.text_styles = [
|
||||
// Silkscreen is GLANCE-ONLY: tabs, group captions, section
|
||||
// headings. Never a paragraph — its `b` and `d` are the same
|
||||
// height as its `n`, which is fine for four letters and hostile
|
||||
// for forty words.
|
||||
(TextStyle::Heading, FontId::new(11.0, display())),
|
||||
// Small headings need distinct letterforms; reserve Silkscreen
|
||||
// for the brand and larger display headings.
|
||||
(TextStyle::Heading, caption(12.0)),
|
||||
// Plex Mono is the value voice: labels on commands, dimensions,
|
||||
// paths, ids.
|
||||
(TextStyle::Button, FontId::new(13.0, FontFamily::Monospace)),
|
||||
@@ -259,7 +255,7 @@ impl ShellTheme {
|
||||
// Plex Sans carries the sentences — the explanatory lines in the
|
||||
// timeline footer and the refusal dialog.
|
||||
(TextStyle::Body, FontId::new(14.0, FontFamily::Proportional)),
|
||||
(TextStyle::Small, FontId::new(11.0, FontFamily::Monospace)),
|
||||
(TextStyle::Small, mono(12.0)),
|
||||
]
|
||||
.into();
|
||||
|
||||
@@ -301,25 +297,27 @@ pub fn display() -> FontFamily {
|
||||
FontFamily::Name("display".into())
|
||||
}
|
||||
|
||||
/// A Silkscreen caption `FontId` at `size`.
|
||||
///
|
||||
/// The design uses exactly three: 8px group captions, 10–11px tabs and pills,
|
||||
/// 22px section titles.
|
||||
/// Small captions use Plex Mono at a readable minimum size. Larger headings
|
||||
/// retain Silkscreen; brand lettering can request [`display`] explicitly.
|
||||
#[must_use]
|
||||
pub fn caption(size: f32) -> FontId {
|
||||
FontId::new(size, display())
|
||||
if size < 14.0 {
|
||||
mono(size)
|
||||
} else {
|
||||
FontId::new(size, display())
|
||||
}
|
||||
}
|
||||
|
||||
/// A Plex Mono `FontId` at `size` — the value voice.
|
||||
#[must_use]
|
||||
pub fn mono(size: f32) -> FontId {
|
||||
FontId::new(size, FontFamily::Monospace)
|
||||
FontId::new(size.max(12.0), FontFamily::Monospace)
|
||||
}
|
||||
|
||||
/// A Plex Sans `FontId` at `size` — sentences only.
|
||||
/// A Plex Sans `FontId` with a 12px floor for readable helper text.
|
||||
#[must_use]
|
||||
pub fn sans(size: f32) -> FontId {
|
||||
FontId::new(size, FontFamily::Proportional)
|
||||
FontId::new(size.max(12.0), FontFamily::Proportional)
|
||||
}
|
||||
|
||||
/// Whether [`install_fonts`] has run on this context.
|
||||
@@ -468,7 +466,7 @@ mod tests {
|
||||
// `·` is in the chrome copy everywhere and is NOT in
|
||||
// Silkscreen. If the display family had no fallback this
|
||||
// would be the notdef glyph.
|
||||
let id = caption(8.0);
|
||||
let id = FontId::new(14.0, display());
|
||||
assert!(
|
||||
f.glyph_width(&id, '·') > 0.0,
|
||||
"the display family has no fallback behind Silkscreen — \
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# VernierCAD: readable type and a clear V
|
||||
|
||||
The app keeps its paper, charcoal, copper, and technical typography. Small
|
||||
captions use IBM Plex Mono with a 12px floor; larger display headings keep
|
||||
Silkscreen. Secondary text has stronger contrast on both light and dark
|
||||
surfaces. The status row fits its available height to prevent clipped text.
|
||||
|
||||
The app mark is a continuous two-tone V: ivory on the left, copper on the
|
||||
right, a single joined point, and two separate measurement ticks. At small
|
||||
sizes the shell omits the ticks. The native painter follows the user's accent.
|
||||
The packaged SVG uses the default copper. The existing maker's mark and Nib
|
||||
remain available for attribution.
|
||||
|
||||
Assets:
|
||||
|
||||
- `packaging/appimage/vernier.svg`: production vector app icon.
|
||||
- `crates/vernier-ui/src/brand.rs`: native, resolution-independent app mark.
|
||||
- `docs/design/verniercad-logo-concept.png`: revised concept board generated
|
||||
with the built-in image-generation tool. Its bold wordmark is the user's
|
||||
preferred design direction; generated lettering is not an identified or
|
||||
bundled font. The shell uses the already bundled Plex Sans for its title.
|
||||
|
||||
Concept prompt: preserve the clean bold technical VernierCAD wordmark;
|
||||
replace the notched caliper symbol with an unmistakable continuous V, ivory
|
||||
left arm and copper right arm, deep triangular opening and one bottom point;
|
||||
move the measurement ticks outside the letter; charcoal tile and cream
|
||||
background; show a small-size proof. No internal slit, notch, or floating bar.
|
||||
|
||||
Validation: 445 active vernier-ui tests passed (three pre-existing ignored
|
||||
tests). The `m1-smoke`, `m4-cutaway`, `m4-edge-pick`, and `ux-sketch-feedback`
|
||||
workflows passed once on RADV with the installed worker. All five affected
|
||||
screenshots were reviewed before updating their reference images; geometry,
|
||||
selection and document assertions stayed enabled. This is a focused visual
|
||||
change check, not a new full-release certification or determinism run.
|
||||
|
After Width: | Height: | Size: 911 KiB |
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" role="img" aria-labelledby="title description">
|
||||
<title id="title">VernierCAD</title>
|
||||
<desc id="description">A dark drafting tile with an amber vernier scale.</desc>
|
||||
<rect width="256" height="256" rx="42" fill="#17191c"/>
|
||||
<path d="M54 64h-18v128h18M202 64h18v128h-18" fill="none" stroke="#f2b84b" stroke-width="14" stroke-linecap="square"/>
|
||||
<path d="M78 92h100M78 128h100M78 164h100" fill="none" stroke="#f4f0e8" stroke-width="12" stroke-linecap="square"/>
|
||||
<path d="M96 78v28M128 78v28M160 78v28M112 150v28M144 150v28" fill="none" stroke="#f2b84b" stroke-width="8"/>
|
||||
<desc id="description">An ivory and copper V on an ink tile, with two precision scale ticks.</desc>
|
||||
<rect width="256" height="256" rx="40" fill="#16181A"/>
|
||||
<path d="M28 56H80L128 164V232Z" fill="#F4F1EA"/>
|
||||
<path d="M128 164L176 56H228L128 232Z" fill="#C77A33"/>
|
||||
<path d="M212 32H230M212 43H230" fill="none" stroke="#C77A33" stroke-width="5"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 686 B After Width: | Height: | Size: 548 B |
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "m1-smoke-push-pull",
|
||||
"notes": [
|
||||
"2026-09-15: PNG updated for readable Plex captions, stronger secondary text, unclipped status text, and the VernierCAD V mark; geometry assertions unchanged.",
|
||||
"2026-09-09: PNG rebaselined for gamma-space UI coverage blending. The",
|
||||
"GPU regression measures half-covered black on white as 127 instead of 187.",
|
||||
"The earlier GL drift measurements below are historical; this golden is RADV-only.",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "m4-cutaway",
|
||||
"notes": [
|
||||
"2026-09-15: PNGs updated for readable Plex captions, stronger secondary text, unclipped status text, and the VernierCAD V mark; section and picking assertions unchanged.",
|
||||
"M4-10 THROUGH THE REAL SHELL: the section command aimed from the ribbon,",
|
||||
"an offset typed into its card, the cutaway chip turned on, and the",
|
||||
"resulting picture asserted by asking the ID buffer what is at the pixel",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "m4-edge-pick",
|
||||
"notes": [
|
||||
"2026-09-15: PNG updated for readable Plex captions, stronger secondary text, unclipped status text, and the VernierCAD V mark; picking assertions unchanged.",
|
||||
"M4-1 THROUGH THE REAL SHELL: an edge and a vertex of the starter block are",
|
||||
"picked in the viewport, and the hover says what is under the pointer before",
|
||||
"each click. Nothing here asserts geometry — no feature is created — because",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"distance": 120
|
||||
},
|
||||
"notes": [
|
||||
"2026-09-15: PNG updated for readable Plex captions, stronger secondary text, unclipped status text, and the VernierCAD V mark; sketch assertions unchanged.",
|
||||
"The pending line snaps to horizontal and the 1mm grid before committing. The screenshot pins its rubber-band outline and snap label.",
|
||||
"Four line clicks make a 40x30mm rectangle. Closing it floats the extrude card (bottom right, off the sketch); the height is typed into it and Enter commits. The 10mm extrusion must export 12000mm^3.",
|
||||
"The RADV golden is exact on its measured adapter: mean 0, hot pixels 0, maximum channel delta 0. Other adapters require their own named budget."
|
||||
|
||||
@@ -27,6 +27,9 @@ for binary in vernier-app vernier-worker; do
|
||||
[[ "$ldd_output" != *"not found"* ]] || { echo "missing runtime library for $binary" >&2; exit 1; }
|
||||
hashes[$binary]=$(sha256sum "$source_bin" | awk '{print $1}')
|
||||
done
|
||||
if [[ -f "$release_dir/vernier.svg" ]]; then
|
||||
hashes[vernier.svg]=$(sha256sum "$release_dir/vernier.svg" | awk '{print $1}')
|
||||
fi
|
||||
if [[ -n "$prefix" ]]; then
|
||||
[[ "$prefix" = /* ]] || { echo "--prefix must be absolute" >&2; exit 1; }
|
||||
data_root=$prefix/share; state_root=$prefix/state; bin_root=$prefix/bin; app_root=$prefix/share/applications
|
||||
@@ -60,6 +63,12 @@ if [[ -e "$target" || -L "$target" ]]; then
|
||||
actual=$(sha256sum "$target/$binary" | awk '{print $1}')
|
||||
[[ "$actual" == "$saved" && "$saved" == "${hashes[$binary]}" ]] || { echo "existing release hash mismatch: $binary" >&2; exit 1; }
|
||||
done
|
||||
if [[ -n "${hashes[vernier.svg]:-}" ]]; then
|
||||
[[ -f "$target/vernier.svg" && ! -L "$target/vernier.svg" ]] || { echo "existing release has no owned icon" >&2; exit 1; }
|
||||
saved=$(awk -F= '$1=="sha256_vernier.svg" {print $2}' "$target/.vernier-release")
|
||||
actual=$(sha256sum "$target/vernier.svg" | awk '{print $1}')
|
||||
[[ "$actual" == "$saved" && "$saved" == "${hashes[vernier.svg]}" ]] || { echo "existing release hash mismatch: vernier.svg" >&2; exit 1; }
|
||||
fi
|
||||
fi
|
||||
if $dry_run; then echo "would install $release_dir as $target"; echo "would activate $current and preserve $previous"; exit 0; fi
|
||||
mkdir -p "$releases" "$bin_root" "$app_root" "$state_root/vernier/logs"
|
||||
@@ -72,6 +81,12 @@ if [[ ! -e "$target" ]]; then
|
||||
[[ "$staged_hash" == "${hashes[$binary]}" ]] || { echo "source changed while staging $binary" >&2; exit 1; }
|
||||
done
|
||||
printf 'version=%s\ncommit=%s\nsha256_vernier-app=%s\nsha256_vernier-worker=%s\n' "$version" "$commit" "${hashes[vernier-app]}" "${hashes[vernier-worker]}" >"$stage/release/.vernier-release"
|
||||
if [[ -n "${hashes[vernier.svg]:-}" ]]; then
|
||||
cp "$release_dir/vernier.svg" "$stage/release/vernier.svg"
|
||||
staged_hash=$(sha256sum "$stage/release/vernier.svg" | awk '{print $1}')
|
||||
[[ "$staged_hash" == "${hashes[vernier.svg]}" ]] || { echo "source changed while staging icon" >&2; exit 1; }
|
||||
printf 'sha256_vernier.svg=%s\n' "$staged_hash" >>"$stage/release/.vernier-release"
|
||||
fi
|
||||
fi
|
||||
mkdir -p "$stage/bin" "$stage/applications"
|
||||
cat >"$stage/bin/vernier" <<EOF
|
||||
@@ -94,13 +109,18 @@ desktop_exec=${desktop_exec//\`/\\\`}
|
||||
desktop_exec=${desktop_exec//%/%%}
|
||||
# Escape the Exec value again for the desktop-file string layer.
|
||||
desktop_exec=${desktop_exec//\\/\\\\}
|
||||
desktop_icon=applications-graphics
|
||||
if [[ -f "$stage/release/vernier.svg" || -f "$target/vernier.svg" ]]; then
|
||||
# Follow the same atomic release pointer as the executable, including rollback.
|
||||
desktop_icon=${current//\\/\\\\}/vernier.svg
|
||||
fi
|
||||
cat >"$stage/applications/vernier.desktop" <<EOF
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=VernierCAD
|
||||
Comment=Parametric solid modeller
|
||||
Exec="$desktop_exec"
|
||||
Icon=applications-graphics
|
||||
Icon=$desktop_icon
|
||||
StartupWMClass=vernier
|
||||
Terminal=false
|
||||
Categories=Graphics;3DGraphics;
|
||||
|
||||