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
34 changes: 25 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ jobs:

- uses: Swatinem/rust-cache@v2

# Real PDFium runtime: mark-pdf integration tests bind to it; tests
# skip gracefully where it is absent (macos/windows domain jobs).
# Real PDFium runtime: mark-pdf integration tests bind to it on
# every platform (tests still skip gracefully where it is absent).
- name: Fetch PDFium runtime
run: ./script/fetch-pdfium.sh

Expand All @@ -39,8 +39,12 @@ jobs:
- run: cargo check --workspace --all-targets
- run: cargo test --workspace --all-targets

domain:
name: ${{ matrix.os }} (domain crates check + test)
# Phase 11 (plan.md §19.2, §20.5): the full workspace — including the
# UI and platform crates — compiles and tests on every platform, with
# the real PDFium runtime fetched per OS so mark-pdf integration tests
# bind it everywhere instead of skipping.
cross-platform:
name: ${{ matrix.os }} (full workspace check + test, PDFium bound)
strategy:
fail-fast: false
matrix:
Expand All @@ -53,8 +57,20 @@ jobs:

- uses: Swatinem/rust-cache@v2

# Domain crates must build everywhere; UI/platform crates may lag
# (plan.md §20.5). PDFium binding is runtime-only, so mark-pdf
# compiles without the binary; unit tests never load it.
- run: cargo check --workspace --exclude mark-app --exclude platform --all-targets
- run: cargo test --workspace --exclude mark-app --exclude platform --all-targets
# bash + curl + tar exist on all three runner images (Git bash on
# Windows); the script detects the platform and fetches the matching
# runtime (dylib on macOS, dll on Windows).
- name: Fetch PDFium runtime
shell: bash
run: ./script/fetch-pdfium.sh

# Proof the runtime actually bound: a green test suite alone can't
# show it (tests skip gracefully without a runtime — that path is
# exactly what this step rules out on Windows, whose archive keeps
# pdfium.dll under bin/, not lib/).
- name: Verify PDFium binding
shell: bash
run: cargo run -p mark-pdf --example check_bind

- run: cargo check --workspace --all-targets
- run: cargo test --workspace --all-targets
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,44 @@ Native Rust, no Electron, no network, fully local.

## Status

Pre-alpha — Phase 0 (bootstrap). See `plan.md` for the full specification
and `documentation.md` for the development log.
Pre-alpha — under active development. See `plan.md` for the full
specification and `documentation.md` for the development log.

## Stack

| Concern | Choice |
| ---------- | --------------------------------------------- |
| UI | [GPUI](https://github.com/zed-industries/zed) via [gpui-kit](https://github.com/longbridge/gpui-kit) + [gpui-omarchy](https://github.com/huacnlee/gpui-omarchy) |
| PDF engine | PDFium via [pdfium-render](https://github.com/ajrcarey/pdfium-render) |
| Platforms | Linux (Omarchy/Wayland) first, macOS and Windows next |
| Platforms | Linux (Omarchy/Wayland) first; macOS and Windows compile+CI-verified |

## Development setup (Linux / Omarchy)

Prerequisites: Rust (rustup), `clang`, `pkg-config`, `libxkbcommon`,
Wayland/EGL development packages. On Arch:
Prerequisites: Rust (rustup), `clang`, `pkg-config`, and the GPUI Linux
backend libraries — `libxkbcommon` (+ X11 variant), `libxcb1`, Wayland
(client + EGL), EGL/GLESv2, and `fontconfig` development packages.

On Arch / Omarchy:

```bash
sudo pacman -S rust clang pkg-config libxkbcommon wayland-protocols mesa
sudo pacman -S rust clang pkg-config libxkbcommon wayland-protocols mesa fontconfig
```

On Debian / Ubuntu (the exact set CI installs):

```bash
sudo apt install clang pkg-config libxkbcommon-dev libxkbcommon-x11-dev \
libxcb1-dev libwayland-dev libegl1-mesa-dev libgles2-mesa-dev \
libfontconfig1-dev
```

## Development setup (macOS / Windows)

Rust (rustup) and — on Windows — the [Git bash](
https://gitforwindows.org/) tools (Git for Windows includes them; used by
`script/fetch-pdfium.sh`). No extra system libraries: GPUI links the
platform frameworks (Metal / D3D via wgpu) out of the box.

Fetch the PDFium runtime (one-time, per platform):

```bash
Expand Down
70 changes: 66 additions & 4 deletions crates/app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,21 @@ pub(crate) enum PendingAction {
}

/// One transient toast notice (plan.md §10, §17): asynchronous status that
/// requires no decision.
/// requires no decision. An optional action renders as an inline button
/// (the export toast's "Show in folder").
#[derive(Clone, Debug)]
struct ToastNotice {
message: SharedString,
kind: ToastKind,
action: Option<ToastAction>,
}

/// An inline affordance on a toast.
#[derive(Clone, Debug)]
struct ToastAction {
label: SharedString,
/// Reveal this path in the platform file manager.
reveal: PathBuf,
}

#[derive(Clone, Copy, Debug)]
Expand Down Expand Up @@ -490,7 +500,7 @@ impl MarkApp {
/// Paths are canonicalized so a relative CLI argument still opens from
/// whatever directory the next launch uses.
fn record_recent(&mut self, path: &Path, cx: &mut Context<Self>) {
let canonical = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
let canonical = platform::canonicalize(path);
self.recent.record(&canonical);
let Some(file) = self.recent_path.clone() else {
return;
Expand Down Expand Up @@ -1489,7 +1499,15 @@ impl MarkApp {
}
let name: SharedString = file_name(&destination).into();
tracing::info!(destination = %name, "Export completed");
self.push_toast(format!("Exported {name}"), ToastKind::Success, cx);
self.push_toast_with_action(
format!("Exported {name}"),
ToastKind::Success,
Some(ToastAction {
label: "Show in folder".into(),
reveal: destination.clone(),
}),
cx,
);
self.export = Some(ExportStatus::Done { name });
if let Some(pending) = self.resume_after_export.take() {
self.perform_pending(pending, cx);
Expand Down Expand Up @@ -1517,6 +1535,17 @@ impl MarkApp {
message: impl Into<SharedString>,
kind: ToastKind,
cx: &mut Context<Self>,
) {
self.push_toast_with_action(message, kind, None, cx);
}

/// [`Self::push_toast`] with an inline action button.
fn push_toast_with_action(
&mut self,
message: impl Into<SharedString>,
kind: ToastKind,
action: Option<ToastAction>,
cx: &mut Context<Self>,
) {
let id = self.next_toast_id;
self.next_toast_id += 1;
Expand All @@ -1525,6 +1554,7 @@ impl MarkApp {
ToastNotice {
message: message.into(),
kind,
action,
},
ToastOptions {
timeout: Some(TOAST_TIMEOUT),
Expand Down Expand Up @@ -1996,11 +2026,43 @@ fn toast_area(
.size(rems(1.))
.text_color(notice.kind.color(theme)),
)
.child(div().child(notice.message.clone())),
.child(div().child(notice.message.clone()))
.children(notice.action.as_ref().map(|action| {
let reveal = action.reveal.clone();
div()
.id(format!("mark-toast-action-{id}"))
.test_support()
.ml_auto()
.text_size(rems(0.75))
.text_color(theme.accent)
.rounded_sm()
.px(rems(0.25))
.hover(|style| style.underline().bg(theme.background))
.on_mouse_down(
MouseButton::Left,
cx.listener(move |_, _: &MouseDownEvent, _, cx| {
reveal_path(reveal.clone(), cx);
}),
)
.child(action.label.clone())
})),
)
}))
}

/// Reveals a path in the platform file manager; failures log and vanish
/// (a missing helper is never worth interrupting the user for).
fn reveal_path(path: PathBuf, cx: &mut Context<MarkApp>) {
tracing::info!(path = %path.display(), "revealing in file manager");
cx.background_executor()
.spawn(async move {
if let Err(error) = platform::reveal_in_file_manager(&path) {
tracing::warn!(%error, "could not reveal in file manager");
}
})
.detach();
}

/// The document workspace: thumbnail sidebar, canvas stage with placed
/// objects, and the assets panel (plan.md §10).
fn workspace(
Expand Down
Loading
Loading