15 Commits
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
e56c3055f2 |
fix(persistence): reliable daemon gate, unarm, per-pane after-load
Addresses the PR #99 review: - HIGH daemon local-only was not reliable: run_daemon sets DaemonMode only after EditorState::new() has run init.lua, so desktop_mode(true) in init saw is_daemon()==false and the raw bindings were ungated. Now save_session/restore_session early-return in Rust when the DaemonMode marker is present — set right after the daemon's new(), so it holds for every save/restore that can run after startup (before-quit hook, manual commands, direct binding calls). - MEDIUM desktop_mode(false) could not unarm startup restore: arm_restore is now a boolean (arm_restore(on)) that sets/removes the marker, and desktop_mode(on) calls arm_restore(on). enable-then-disable no longer restores. - MEDIUM/LOW same-file multi-pane missed per-window overlays: restore now fires buffer.after-load once PER LEAF (per window), not once per buffer. Syntax attaches its overlay to the active window, so each pane gets its own; LSP attach_buffer is idempotent, so the same file in two panes attaches LSP once but syntax to both. - MEDIUM hidden restored buffers: documented as registry-only in v1 (they are live/openable/in recentf, but do not fire after-load, so they attach syntax on first visit via after-switch and LSP when next shown). Full initial attach for hidden buffers is deferred. Noted in the framing + a code comment. - LOW trailing whitespace in docs/desktop-save-framing.md. Tests (desktop_acceptance now 11): same_file_..._fires_per_pane asserts after-load fires twice for two panes of one file; daemon_mode_disables_ save_and_restore; disabling_desktop_mode_unarms_restore. Gates: fmt + workspace clippy clean; lib 1487; crdt 1658; desktop 11; persistence 5; m4 90; GPU 58; git diff --check clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
|
|
|
3607df0afe |
feat(persistence): desktop-save --- buffers + layout + positions (Arc 3 phase 2)
Framing: docs/desktop-save-framing.md (Q#DS1-10). Save the open file
buffers, window layout, and per-window positions on quit; rebuild them
on startup. Emacs desktop.el, opt-in, local-mode only in v1.
All-Rust (Q#DS1) --- the core window enums are not serde and there is
no Lua tree API, so the layout mirror + structural rebuild live in Rust.
Lua adds only the opt-in switch and manual commands.
src/desktop.rs (new):
- Serde mirror (SavedDesktop / SavedBuffer / SavedNode / SavedLeaf /
SavedOrientation): every open file buffer (visible OR hidden, so a
switched-away file survives), the layout tree with orientation +
weights + nesting, per-leaf cursor/view_top, and an active-leaf
preorder index with a nearest-neighbor fallback (Q#DS10).
- session_key: SHA-256, name.<hex> when a socket name is set else
cwd.<hex> (charset-safe for the pmacs.state key).
- save_session / restore_session take the &Lua that carries the
SharedCore / StateDir / LocalInstanceInfo app-data, so they run
identically from a pmacs.session.* binding and the startup trigger.
- restore ordering (Q#DS3): open all buffers; prune EVERY window of the
old LOCAL layout (not just scratch); rebuild the tree; then per leaf
in preorder activate its window and fire buffer.after-load once per
newly-loaded buffer (hooks read active state), and write the exact
cursor/view_top AFTER so desktop wins over saveplace (same file in two
panes keeps distinct positions). A missing file collapses its leaf.
src/editor_core.rs: get_or_load_buffer(path) --- find_by_path else
load fresh, WITHOUT switching the active window; returns (id, newly).
src/lua_bindings: pmacs.session.{save_desktop, restore_desktop,
arm_restore, is_daemon}; DesktopRestoreArmed + DaemonMode markers;
fire_after_load_hook seam.
builtin/runtime/desktop.lua: pmacs.session.desktop_mode(on) wires
before-quit save + arms restore; desktop-save / desktop-restore
commands. No-op under a daemon (Q#DS9).
Startup trigger (Q#DS7): editor::run captures had_file before the match
consumes `file`, and restore_desktop_if_armed runs INSIDE the RunLocal
arm (after attach dispatch) so a hand-off to attach never populates an
EditorState it is about to drop. Daemon marks DaemonMode → desktop
stays local-only.
Tests: src/desktop.rs units (tree collapse, active-leaf fallback,
key/json round-trip) + tests/desktop_acceptance.rs (9): nested weighted
round-trip, hidden-buffer survival, after-load-active probe, same-file
two-pane distinct positions, missing-file collapse + focus fallback, no
orphan windows, name-vs-cwd key scoping, modified warning, startup gate.
Gates: fmt + workspace clippy clean; lib 1487; crdt 1658; desktop 9;
persistence 5; m4 90; m8 daemon 10/15; query-replace/completion/
listview/overlay/cua green; GPU 58; git diff --check clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
|
|
d5d75e63ed |
fix(persistence): symlink confinement, real test-inertness, view_top restore
Addresses the PR #98 review: - HIGH symlink escape: resolve() did only a lexical starts_with, so a base/autosave symlink -> /tmp/out let state.write("autosave/x") write outside the state dir. Now every existing component the key adds under base is lstat'd and a symlink (live OR broken) is rejected; base itself may still be a symlink (dotfile-managed ~/.local/state). Unix symlink escape test added (live + broken + plain-subdir-ok). - MEDIUM integration-test state leak: the state/history dir wiring moved out of EditorState::new() into EditorState::install_state_dirs(), called only by the real entry points (editor::run, run_daemon). Unit AND integration tests construct EditorState directly, so they never configure a real dir -> default-on recentf/saveplace write nothing to ~/.local/state/pmacs during cargo test. The inertness test now asserts a bare new() leaves StateDir unconfigured (direct proof). - MEDIUM saveplace never recorded view_top: exposed the missing pmacs.editor.view_top() getter (set_view_top existed but no getter, so the Lua stored 0). saveplace now records+restores the viewport; acceptance asserts view_top restores, not just the cursor byte. - MEDIUM/LOW relative XDG_STATE_HOME / PMACS_STATE_HOME: a relative value rooted state at a cwd-relative pmacs/... (same footgun class as the empty case). Both are now required absolute; relative values are ignored (XDG falls through to HOME). Test added. - LOW trailing blank line at recentf.lua EOF (git diff --check). Gates: fmt + workspace clippy clean; lib 1483; crdt 1654; persistence 5; m4 90; m8_1/m8_2 daemon 10/15; query-replace/completion/listview/overlay/ cua green; GPU 58; git diff --check clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
|
|
|
4dd4b9ab97 |
feat(persistence): state foundation + saveplace + recentf (Arc 3 phase 1)
Framing: docs/persistence-framing.md. The four Rust primitives the
Lua-vs-Rust scout said were unavoidable, plus two Lua policy modules.
Rust:
- src/state.rs: state_dir(xdg,home) returning .../pmacs (generalizes
the baked-in history path). Deliberate empty-XDG fix (Q#PS2): a blank
XDG_STATE_HOME fell through to a RELATIVE pmacs/... path (a cwd-write
bug); now treated as absent so it falls to HOME. Confined key->file
store: validate_name rejects absolute / .. / empty / // / control
chars, plus a canonical-prefix belt; read/write/remove go through
file_io::save_atomic, never raw io.open. A PMACS_STATE_HOME override
lets CI / privacy-conscious users / integration harnesses redirect
all state to a scratch dir. History routed through the shared
resolver so it honors the override too.
- pmacs.state.{write,read,remove,path,available}: a no-op when the
state dir is unconfigured (cfg(test) / no HOME), so default-on
builtins write nothing in the lib suite. Configured once at startup
like history_dir, skipped under cfg(test).
- pmacs.editor.goto_byte / set_view_top: byte-exact restore (switch
zeroes the cursor).
Lua (builtin/runtime):
- saveplace.lua: record the active file's cursor+view_top on
before-save / before-quit; restore on after-load. LRU-capped places
state file. On by default; pmacs.saveplace.enable(false).
- recentf.lua: MRU record on after-load AND after-switch (re-visits
refresh the order); deduped/capped recentf file; a recent-files
command bound C-x C-r opens the minibuffer picker.
Tests: state.rs units (validate/resolve/round-trip/empty-XDG),
tests/persistence_acceptance.rs (state round-trip + confinement
rejections, inert-when-unconfigured, recentf MRU/dedup, saveplace
restore-on-reload, disable knob) injecting a tempdir state root. One
describe-hook test made robust to a builtin now subscribing to
buffer.before-save.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
|
|
7e7b3f2dcd |
feat(edit): query-replace (M-% / C-M-%) — Arc 2
Emacs query-replace, built on isearch with zero protocol change (framing: docs/query-replace-framing.md). - search.rs: find_first_from (literal) + find_first_regex_from (cached engine, zero-width-skip) + compile_search_regex (shared smart-case compile). Q#QR2's forward-scan-past-replacement primitive. - QueryReplaceSession + core methods (editor_core.rs): begin (invalid regex refuses, Q#QR2), replace/skip/all/replace-and-quit/finish; matches run forward from next_from on the LIVE buffer, so offset shifts and never-re-matching-replacements (a->aa) fall out for free; current match highlighted via a single-element search_store set (SearchMatchActive, both frontends free) + cursor reveal; quit keeps replacements, only nothing-matched restores origin (Q#QR10). - Dispatcher shadow (editor.rs): QueryReplaceKey (y/SPC, n/DEL, !, ., q/RET/Esc/C-g) + dispatch_query_replace_key, the 5th modal shadow; added to dispatch_idle disjunction (GPU round-trips keys) and fires buffer.after-edit itself (Q#QR1 — a shadow returns before the normal post-command check; once per !-batch). - Lua: ed.query_replace_start/query_replace_active; query-replace / query-replace-regexp commands (chained minibuffer.read, separate from/to history buckets, empty-from reject / empty-to deletion); M-% / C-M-% bindings. - Per-match prompt via core.status → v15 StatusFacts.message band. Tests: 7 core unit + 11 dispatch_key acceptance (replace/skip/!/./quit, nothing-matched restore, empty-to deletion, a->aa non-loop, regex incl invalid, after-edit fires, dispatch_idle gate, explicit M-% AND C-M-% binding tests) + 5 search unit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
|
|
|
5c5caa9482 |
fix(panels): follow active buffer on semantic frontends; re-attach overlays on switch
Two PR #94 validation findings. 1. (High, blocking) GPU stuck after leaving a panel: the GPU only swaps its displayed buffer on BufferSnapshot, and the daemon only sent one on the first CRDT upgrade (F29's ensure returns None for an already-backed buffer). A panel's q / RET switched the daemon's active buffer back to the already-known source and sent nothing -- the GPU kept rendering the panel while input targeted the source: a typing-into-a-buffer-you-can't-see hazard. Fix: the per-tick loop now FOLLOWS each replica frontend's own active buffer -- when it differs from the last snapshot sent to that frontend, ship that buffer's snapshot to that frontend only (the F29 broadcast records itself so the upgrade tick doesn't double-send). First-tick send also repairs the attach-time last-snapshot-wins ambiguity. Snapshot export extracted and shared with the F29 broadcast; per-fid state cleaned on both detach paths. 2. (High, wider than reported) 'LSP doesn't activate on navigate': switch_active_buffer clears the window's overlays, and the runtime dedup tables (highlighted_buffers, styled_buffers, diag_viewed_buffers) blocked re-attachment -- so EVERY buffer switch (plain C-x b included, long-latent) permanently stripped syntax color, LSP semantic style, and diagnostic underlines; verified: overlay kinds [syntax-highlight, lsp-style, diagnostic] -> [] after one away-and-back. Fix: a new additive buffer.after-switch hook, fired by the window.switch_buffer binding and find_or_open's existing-buffer branch; syntax.lua and lsp.lua subscribe and re-push their views (the just-cleared window makes that exactly-once per switch; fresh loads keep firing after-load). Regression: tests/overlay_reattach_acceptance.rs (double round-trip counts exactly one highlight overlay; panel q restores styling). The daemon follow path is validated live (daemon + GPU) -- its unit seam is the shared export helper. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|
|
|
b25b47334d |
feat(panels): listview module, Q#P6 round-trip seam, references panel
Arc 1b phase 1 (framing: docs/lsp-panels-framing.md).
Q#P6 (the one Rust change): EditorCore.round_trip_buffers +
pmacs.buffer.set_round_trip_input(buf, on); dispatch_idle() reports
false while a marked buffer is active, so semantic frontends'
optimistic-apply stays off -- RET reaches a panel's buffer-local visit
binding instead of locally inserting a newline, and typing dispatches
into the edit path where the read-only intercept rejects it (a CRDT
import would bypass the intercept chain entirely). Pruned on kill.
Q#P1/P2/P3: builtin/runtime/listview.lua generalizes the *buffer-list*
idiom -- pmacs.listview.open{name, header, rows, on_visit, on_refresh}
owns ensure-buffer (recreates if user-killed), wholesale render with
bypass_intercept, line->item map, buffer-local RET/SPC/n/p/g/q keymap,
previous-buffer capture + q restore (never another panel; scratch
fallback), cursor re-seat after render, the read-only intercept, and
the Q#P6 mark. Panels are buffers: both frontends render them with
zero protocol change.
Q#P4: lsp.find-references (M-?) opens *references* -- one row per
location, paths shortened against the project root, RET visits via the
shared SP-4 template (jump ring, find_or_open, cursor walk; extracted
as visit_location for the phase-2 outline to reuse).
Acceptance: tests/listview_acceptance.rs -- open/seat/visit, header
non-visitable, q restore, read-only rejection, dispatch_idle gate,
refresh re-render + re-seat.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
|
|
c01cfd1e93 |
test(completion): popup acceptance suite; fix pmacs.completion table clobber
Seven end-to-end tests through dispatch_key: dabbrev auto-open + TAB accept, C-n/RET second-candidate accept, Esc dismiss with fall-through typing (and no same-edit reopen), Home-breaks-anchor validation close, yank-shaped edits never auto-open (Q#C9), C-M-i below the threshold, and ctx.uri scoping through the Lua provider surface. The suite immediately caught a real wiring bug: install_completion (M4.7, runs at make_lsp_manager time) built pmacs.completion with a fresh lua.create_table(), clobbering the popup bindings installed at editor-attach time --- popup_visible was nil at runtime and the driver hook errored silently into *errors* on every edit. It now merges into the existing table, the same idiom as install_completion_framework, so installer order no longer matters. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|
|
|
53d771a935 |
feat(completion): Lua driver, popup bindings, LSP scoping + flush seams
Q#C1/C9: builtin/runtime/completion.lua reconstructs typing intent
from state (buffer.after-edit has no payload): a {buffer, cursor}
snapshot recognizes the single-byte-advance typing signature, so
paste/undo/kill/remote edits never auto-open; prefix >= 2 opens off
the synchronous providers, server trigger chars open a pending session
that materializes when the LSP answer lands; refresh-on-typing
re-derives the prefix from the text; a core-closed popup suppresses
reopen off the same edit (the accept case). completion.at-point on
C-M-i covers deliberate invocation; the driver filters collect() to
score >= 0 (collect keeps non-matches, merely sorted last).
Q#C8: CompletionContext gains uri; the built-in LSP provider scopes to
it (legacy global drain only when absent); Lua providers get uri as a
trailing ninth positional arg; context_for can now express char
triggers + uri. pmacs.lsp.attachment_for_request() exposes the
flushing accessor (attached_for_active) so completion requests answer
against current text, not the debounced didChange backlog.
Q#C2 write path: pmacs.completion.popup_show/popup_hide/popup_visible
publish into the core session (kind tags shared with collect() rows,
so driver code passes rows straight through).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
|
|
9ae381f740 |
feat(tui): relative + hybrid line-number modes (sub-arc 3, TUI half)
Extend the gutter with the last two of the framed modes (Q#UX4): - Relative: each line shows its distance from the cursor line (cursor = 0). - Hybrid: cursor line shows its absolute number, others relative (Vim number + relativenumber). `LineNumberMode` gains `Relative`/`Hybrid` + `number_for(line, cursor_line)` (the per-line displayed value) and `is_on()`. `paint_line_number_gutter` now derives each number from the mode and the cursor's buffer line (`text_view.line_at_offset(cursor)`); the TUI re-renders the whole frame on cursor motion, so relative numbers track the cursor for free. Gutter width is sized by `digits(line_count)` for every on-mode, so the text never jitters as the cursor moves. Mode selection (chosen over a 4-way cycle): `window.toggle-line-numbers` stays a binary off/absolute toggle; a new `window.set-line-numbers` opens the minibuffer with an arrow-navigable completion dropdown (off|absolute|relative|hybrid) to pick a mode directly. `set_line_numbers` accepts all four; the getter returns them. No protocol change here — the GPU half (which needs the mode over the wire, protocol v14) follows. Test: number_for across all modes. fmt + clippy clean both flavors; 1446 lib tests pass. Needs a TUI eyeball. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014TXbAwk27agwhrNNrhLi2U |
|
|
|
fae7ed3fd0 |
feat(tui): line-number gutter (UX arc sub-arc 1, TUI half)
Introduce a reserved left gutter column with absolute line numbers in the
TUI/grid frontend — the foundational piece of the UX arc (docs/ux-arc-
framing.md). Default OFF (Emacs tradition), so zero layout/coordinate
change until a window opts in.
- window.rs: LineNumberMode { Off, Absolute } + per-window `line_numbers`
field + `gutter_width()` (digits(line_count) + PAD) + `decimal_digits`.
- editor.rs: the gutter is one viewport shift at the paint site
(cell_origin.col += gutter_w, cell_size.cols -= gutter_w) — every
viewport-relative painter (text, syntax, diag underline, search) stays
gutter-agnostic. The sites that read rect.origin.col directly get a
manual +gutter_w: cursor placement, local selection, mouse hit-test
(a gutter click maps to line start, Q#UX6). paint_line_number_gutter
writes right-aligned dim digits alloc-free.
- overlay_paint.rs: remote-presence cursor/selection shift by gutter_w.
- Lua: pmacs.window.set_line_numbers/line_numbers +
window.toggle-line-numbers command.
No protocol/daemon change (frontend-local, Q#UX1). Tests: gutter render
(right-aligned digits + past-EOF blanks) + decimal_digits.
Validated: fmt clean; clippy --lib clean both flavors; 1439 lib tests pass
both flavors. Needs a human eyeball (coordinate-math change) before the PR.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TXbAwk27agwhrNNrhLi2U
|
|
|
|
296cf34ae9 |
refactor(lua): re-export install_* wiring fns to preserve the public API
Review follow-up on the F-016 split. install_diag / install_project_index / install_mcp were `pub fn` reachable at crate::lua_bindings::install_* be- fore the split, but moving them into private child modules dropped those paths without a re-export — shrinking the public API, which the split is supposed to preserve. (They take crate-internal handle types so no external caller can invoke them, and none does, so nothing actually broke — but the paths should still resolve.) Re-export all three alongside the factories/handles already re-exported, restoring the paths for the two already-merged tranches (diag, index) too. Deliberately narrowing these to pub(crate) is left as a separate change. Validated: fmt clean; clippy --lib clean under both Lua flavors; full lib suite 1437 passed / 0 failed under luajit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014TXbAwk27agwhrNNrhLi2U |
|
|
|
e8e56bdbf1 |
refactor(lua): extract pmacs.mcp into its own module (F-016, tranche 2)
Third tranche of the F-016 split. Extract the pmacs.mcp surface (MCP client bindings) from src/lua_bindings/mod.rs into src/lua_bindings/mcp.rs, verbatim. Corrected model (see framing): a helper-hoist is NOT a prerequisite for most domains. The contamination that stopped parse/theme bites only when a shared helper is *defined inside* the range being extracted. A domain that merely *uses* a cross-section helper reaches it via `super::` (parent-private access). So mcp extracts cleanly: all its items are self-contained, and it reaches the JSON converters (still in the lsp section) via super::json_to_lua / lua_to_json, and SharedProcessSupervisor via super::. The JSON-helper hoist is deferred to the tranche that extracts lsp itself (where they're defined). mod.rs declares `mod mcp;` and re-exports make_mcp_manager (external caller editor.rs) and McpServerIdLua — the latter to preserve its public-API path crate::lua_bindings::McpServerIdLua (moving it into a private module had dropped it from the crate surface; the split must not shrink the public API). Pure code motion, no behavior change. mod.rs: 14603 → 14020 lines. Validated: fmt clean; clippy --lib clean under both Lua flavors; full lib suite 1437 passed / 0 failed under both luajit and lua54. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014TXbAwk27agwhrNNrhLi2U |
|
|
|
df35e03ecf |
refactor(lua): extract pmacs.index into its own module (F-016, tranche 1)
Second tranche of the F-016 split. Extract the pmacs.index surface (the project symbol-index bindings) from src/lua_bindings/mod.rs into src/lua_bindings/index.rs, moved verbatim. index is the one genuinely clean remaining leaf: its private helpers (symbol_kind_from_lua, lua_symbol_from_table, search_hit_to_lua) are used only within its own range, and it has zero shared-core coupling — it depends only on crate::project_index, mlua, and std, reaching one stranded helper (lua_to_json, still in the lsp section) via `super::`. mod.rs declares `mod index;` and re-exports `SharedProjectIndexer` + `make_project_indexer` via `pub use`, so the crate::lua_bindings::… paths in editor.rs and completion_framework.rs (and an in-file completion- framework use) stay valid — no external file changes. Pure code motion, no behavior change. mod.rs: 14986 → 14603 lines. While vetting the next leaves I found the recon under-counted the misplaced shared helpers: parse/theme, window, and minibuffer trail off into shared style/color, caller_source, and command/menu helpers, so a dedicated helper-hoist tranche must precede them (framing tranche plan updated). This tranche stops at index rather than force a contaminated extraction. Validated: fmt clean; clippy --lib clean under both Lua flavors; full lib suite 1437 passed / 0 failed under both luajit and lua54. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014TXbAwk27agwhrNNrhLi2U |
|
|
|
d002b7da71 |
refactor(lua): split lua_bindings.rs into a module dir; extract pmacs.diag (F-016, tranche 0)
First tranche of the F-016 split of the 15k-line src/lua_bindings.rs. Deliberately minimal — it validates the mechanics before bulk moves. - Convert src/lua_bindings.rs → src/lua_bindings/mod.rs (the `pub mod lua_bindings;` in lib.rs resolves to mod.rs unchanged). - Extract the pmacs.diag surface (diagnostic_to_lua + install_diag) into src/lua_bindings/diag.rs, moved verbatim. mod.rs declares `mod diag;` and its one internal call site is now `diag::install_diag(...)`. Pure code motion: no logic, signature, or behavior change. diag.rs reaches shared-core items (BufferIdLua, SharedCore) via `super::` — a child module can see its ancestors' private items, so no visibility widening was needed; install_diag's only caller is mod.rs itself, so no re-export either. The Lua-visible pmacs.diag.* API is byte-for-byte unchanged. mod.rs: 15202 → 14986 lines. Framing + tranche plan: docs/lua-bindings-split-framing.md. Validated: fmt clean; clippy --lib clean under both Lua flavors; full lib suite 1437 passed / 0 failed under luajit AND lua54 (the tests drive pmacs.diag.* through the Lua VM — same outcomes, code relocated). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014TXbAwk27agwhrNNrhLi2U |
Renamed from src/lua_bindings.rs (Browse further)