ckVision

Platform services

The UI library is intentionally host-driven. Your application chooses the terminal backend and clock; filesystem and clipboard operations are explicit services. This keeps UI code deterministic and makes a real application and a headless test share the same view graph.

ServiceInteractive POSIX hostInteractive Windows hostDeterministic test hostClient use
terminalterm::PosixTerminalterm::WindowsTerminalterm::HeadlessTerminalconstruct Application; run/poll/present
clockterm::PosixClockterm::WindowsClockManualClocktimers and application time
clipboardTerminalClipboardWriter (OSC 52)WindowsClipboardWriter or the default terminal adapterApplication internal clipboardtext editor controls
filesystemterm::PosixFileSystemterm::WindowsFileSystemMemoryFileSystemFile Browser, file/directory dialogs

The File Browser accepts FileSystem&, so its master/detail wiring is identical against a real disk and the deterministic tree used for screenshots. It never lets TreeView or ListView query the disk themselves.

Owned-process observations

core::ProcessId holds positive POSIX identities and the full Windows DWORD range, with -1 meaning no process. It is observation-only, never authority to terminate a process. TerminalSubsession::process_resources() returns typed, clock-free core::ProcessResources; mirrors and emulators report Unsupported. Windows sessions observe their own job, without exposing its handle to callers.

Native hosts may also call term::sample_windows_process(id) or borrow an explicit job with term::sample_windows_job(job). A null job fails rather than implicitly selecting the caller's job. Available, Partial, Gone, Failed and Unsupported are distinct; unavailable fields are nullopt, not numeric zero. The first native error is retained. No process privileges are enabled.

CPU is cumulative user plus kernel nanoseconds, not a percentage. A job's OwnedJobLifetime CPU includes exited children; memory/counts describe observed live membership, including nested jobs. Compute rates with your injected clock. RSS sums current resident working sets (shared pages may count more than once). Private RSS sums non-sharable resident pages, not virtual private commit charge or proportional shared memory. Both come from the documented working-set query supported throughout the native ConPTY OS range, without assuming newer EX2 support. Growing lists retry boundedly; an unreadable member makes the memory total absent rather than publishing an incomplete sum as though it were full. Observations are not an atomic snapshot across processes. Exits during sampling are skipped, and opened processes are checked against the owned job to exclude PID reuse outside it. The samplers do no process control, rate timing or caching.

On Windows, term::WindowsClipboardWriter exports UTF-8 as native CF_UNICODETEXT. Each write owns an invisible message-only window on the constructing thread, rather than borrowing a console-host window or registering a global window class. Construct, call and destroy the adapter on that thread. It can be injected directly into Application; WindowsTerminal owns the same adapter for the default clipboard path. The publication window is destroyed before returning: the system retains the immediately rendered Unicode data, and later writers need not send ownership messages to an unpumped terminal application's window. Writes return ClipboardWriteResult and never retry waiting for another application to unlock the clipboard. Ok means native publication (or an in-memory write) succeeded. Submitted means the complete OSC 52 request was written; a terminal protocol submission does not confirm the remote clipboard. Unsupported, InvalidText, Unavailable and Error distinguish refusal, invalid input, contention and system failures. native_error retains the backend's error number where available; on failure, external_state_may_have_changed warns that earlier native emptying or partial terminal output may already have affected the host. Invalid UTF-8 and embedded NUL are rejected before opening it; Application's internal copy remains available when native export fails. After EmptyClipboard succeeds, a later OS publication failure may leave the system clipboard empty. No read/import or delayed-rendering message loop is introduced. Native tests use a separate window station so they never replace the user's desktop clipboard.

Application::set_clipboard_text() first stores the internal text and returns the bridge's exact result. last_clipboard_export_result() is empty before the first export. set_clipboard_export_handler() installs one per-Application handler, including for copies initiated by focused editor controls. An empty handler removes it. Handlers receive a value snapshot and may replace themselves safely; use post() for modal/error UI after the copy event. Paste imports neither re-export nor replace the last export result. Record/replay terminals preserve every copy attempt and its outcome, so a failure-driven UI can be replayed without accessing the OS clipboard.

FileEditorController uses the same injected boundary for read_file(), fingerprint(), and write_file_atomic(). A save supplies the fingerprint it loaded; a changed-on-disk file returns a conflict rather than being silently overwritten. MemoryFileSystem implements these operations for deterministic editor lifecycle tests. Applications that own a directory tree can call create_directories(); it creates missing parents idempotently and remains behind the same injected platform boundary. See Editor for the document/controller composition.

term::WindowsFileSystem supplies the real disk service on Windows. Names are strict UTF-8, converted to native wide drive/UNC paths; long paths use extended spelling without requiring an application manifest. normalize_path resolves native relative names and dot components, retains the drive/share root, and uses '/' separators. parent never navigates above that root; join accepts a child fragment, not an absolute second argument. Invalid UTF-8/NUL, wildcards, device namespaces and alternate streams are rejected.

Listing sorts directories first and then files in UTF-8 byte order. Queries and reads follow links. Whole-file reads fingerprint the opened handle, not the replaceable path; ordinary write handles are excluded during the read. Conditional saves hold a persistent .ckvision-write.lock per directory, check the expected revision, write/flush an exclusive random sibling and publish by native rename. Cooperating adapters/processes therefore cannot both save the same stale revision. Opaque revision tokens include native file identity, size/timestamps and streamed SHA-256 bytes, detecting same-size changes even when native timestamps are identical. Fingerprint-only queries read/hash the file without retaining its contents; whole-file reads hash the same chunks they return. This is not a portable content-addressing format. The lock basename is reserved and the service does not clean or replace it.

Writes refuse a final-component reparse point, directories and native access/read-only/sharing failures. On failure the target is never partially copied over or removed as a fallback. Existing discretionary permissions (DACL) are preserved; new files inherit directory permissions. The new file's owner is its creator, and other metadata/alternate streams are not preserved by this byte-file service. Native filesystems must support the revision and rename operations; unsupported operations report failure. No administrator privileges are enabled. This is cooperative serialization: arbitrary external path replacements or mapped-memory writes are not locked by the adapter.

    window->set_min_size(Size{40, 10});
    // Keeps filling the content area as the terminal grows, rather
    // than staying pinned at whatever size it happened to be created
    // at (M8 WP-4) — the natural policy for a single-window,
    // fills-the-desktop application like this one.
    window->set_grow_policy(widgets::DesktopGrowPolicy::KeepFilling);

    auto tree = std::make_unique<widgets::TreeView>();
    tree_ = tree.get();

    widgets::TreeNode root;
    root.label = root_path_;
    root.user_data = root_path_;
    populate_children(fs_, root);  // eager for the root only, so it can start expanded
    root.expanded = true;
    std::vector<widgets::TreeNode> roots;
    roots.push_back(std::move(root));
    tree_->set_roots(std::move(roots));

    auto file_list = std::make_unique<widgets::ListView>();
    file_list_ = file_list.get();

    tree_->on_selection_changed = [this](widgets::TreeNode&) {
        refresh_file_list_for_selected_node();
    };
    // Lazy population (M10/WP-22): fires the first time the user
    // expands a node whose children were never listed.
    tree_->on_expand_request = [this](widgets::TreeNode& node) { populate_children(fs_, node); };

    // Splitter (M10/WP-19) replaces the old fixed 50/50 Row: the split
    // starts at the same exact 50/50 ratio (Splitter's own default),
    // and Left/Right now lets the user adjust it.
    auto content = std::make_unique<widgets::Splitter>(window->content_rect(), std::move(tree),
                                                         std::move(file_list));
    splitter_ = content.get();
    window->set_content(std::move(content));

    // The selected directory's full path, shown live on the window's
    // own bottom border via Window::add_frame_overlay — exactly the
    // "current line in a text input window" pattern the overlay slot

Deterministic File Browser capture

Terminal profiles describe what the host is known to support; they are not a license to inspect environment variables inside a widget. See terminal profiles, graphics, and input decoding for the protocol-facing reference material.