Skip to content

Commit f7a44a7

Browse files
committed
feat(serve): artifact view source link opens the /source viewer at the artifact line (REQ-243, #623)
The artifact detail view showed the source file but the link was a dead `href="#"` that did nothing in a plain browser — it only worked via the VSIX editor shim. It now deep-links into the built-in `/source` viewer at the artifact's definition line (hx-get + hx-push-url, scrolling to the line), while keeping the `data-source-file`/`data-source-line` attributes so the VSIX webview shim still opens the file in the editor. The VSIX webview loads no htmx, so the two runtimes don't collide: browser follows hx-get, VSIX follows the shim. Off-by-one: the `/source` viewer numbers rows 1-based, so the browser scroll target is `source_line + 1`; `data-source-line` stays 0-based for the VSIX host, which consumes it as a 0-based position. Confirmed with a live server: the detail link carries hx-get/href to /source, that route returns 200 for REQ-001, and the scroll target lands on the artifact's line. Full serve_integration suite plus cargo clippy --all-targets -- -D warnings and cargo fmt --check all green. Implements: REQ-243 Verifies: REQ-243 Refs: FEAT-001
1 parent f2329f0 commit f7a44a7

3 files changed

Lines changed: 62 additions & 9 deletions

File tree

artifacts/requirements.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7623,7 +7623,7 @@ artifacts:
76237623
- id: REQ-243
76247624
type: requirement
76257625
title: rivet serve artifact view opens the source file at the artifact location
7626-
status: proposed
7626+
status: verified
76277627
description: "In the `rivet serve` artifact view the source file is shown but clicking it does not open the file at the artifact's location, even though sources are configured — this deep-link only exists in the VSIX extension today. Wire the same source open-at-location behaviour into the dashboard. #623."
76287628
provenance:
76297629
created-by: ai-assisted

rivet-cli/src/render/artifacts.rs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -533,24 +533,41 @@ pub(crate) fn render_artifact_detail(ctx: &RenderContext, id: &str) -> RenderRes
533533
})
534534
});
535535

536-
// Source file link (shown at top for quick access)
537-
// Uses data-source-file + data-source-line attributes — the VS Code
538-
// nav shim in shell.ts picks these up and opens the file at the
539-
// exact line of the artifact definition.
536+
// Source file link (shown at top for quick access).
537+
//
538+
// REQ-243 (#623): the link now navigates the dashboard to the built-in
539+
// `/source` viewer at the artifact's definition line — previously it was a
540+
// dead `href="#"` that did nothing in a plain browser and only worked via
541+
// the VSIX editor shim. Two runtimes, one anchor:
542+
// * Browser dashboard (htmx loaded): `hx-get` swaps in the `/source`
543+
// view and the `onclick` scrolls to the artifact's line.
544+
// * VSIX webview (no htmx): the `shell.ts` shim intercepts the
545+
// `data-source-*` attributes and opens the file in the editor.
546+
// Off-by-one: `/source` row anchors are 1-based, so the browser fragment is
547+
// `source_line + 1`; `data-source-line` stays 0-based for the VSIX host.
540548
let source_link = if let Some(ref sf) = source_file {
541549
let filename = std::path::Path::new(sf)
542550
.file_name()
543551
.and_then(|n| n.to_str())
544552
.unwrap_or(sf);
553+
let encoded_path = urlencoding::encode(sf);
545554
let line_attr = source_line
546555
.map(|l| format!(" data-source-line=\"{l}\""))
547556
.unwrap_or_default();
557+
let onclick = source_line
558+
.map(|l| {
559+
format!(
560+
" onclick=\"setTimeout(function(){{var e=document.getElementById('L{}');if(e)e.scrollIntoView({{behavior:'smooth',block:'center'}})}},200)\"",
561+
l + 1
562+
)
563+
})
564+
.unwrap_or_default();
548565
format!(
549566
" <span class=\"meta\" style=\"float:right;font-size:.85rem\">\
550-
<a href=\"#\" data-source-file=\"{}\"{} title=\"Open source file\">&#128196; {}</a></span>",
551-
html_escape(sf),
552-
line_attr,
553-
html_escape(filename),
567+
<a class=\"source-ref-link\" hx-get=\"/source/{enc}\" hx-target=\"#content\" hx-push-url=\"true\" href=\"/source/{enc}\" data-source-file=\"{sf_esc}\"{line_attr}{onclick} title=\"Open source file\">&#128196; {fn_esc}</a></span>",
568+
enc = encoded_path,
569+
sf_esc = html_escape(sf),
570+
fn_esc = html_escape(filename),
554571
)
555572
} else {
556573
String::new()

rivet-cli/tests/serve_integration.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,42 @@ fn artifact_detail_has_oembed_discovery_link() {
807807
child.wait().ok();
808808
}
809809

810+
/// REQ-243 (#623): the artifact detail view's source link must navigate the
811+
/// dashboard to the built-in `/source` viewer at the artifact's definition
812+
/// line (previously a dead `href="#"` that only worked in the VSIX editor
813+
/// shim). It must also keep the `data-source-*` attributes so the VSIX shim
814+
/// still opens the file in the editor.
815+
///
816+
/// rivet: verifies REQ-243
817+
#[test]
818+
fn artifact_detail_source_link_opens_source_view() {
819+
let (mut child, port) = start_server();
820+
821+
let (status, body, _headers) = fetch(port, "/artifacts/REQ-001", false);
822+
assert_eq!(status, 200);
823+
824+
// Browser path: deep-links into the /source viewer via htmx (no dead #).
825+
assert!(
826+
body.contains("hx-get=\"/source/") && body.contains("href=\"/source/"),
827+
"artifact detail source link must navigate to the /source viewer, \
828+
not a dead href=\"#\""
829+
);
830+
// VSIX path: the editor shim still gets the file + line attributes.
831+
assert!(
832+
body.contains("data-source-file="),
833+
"artifact detail source link must keep data-source-file for the VSIX shim"
834+
);
835+
// REQ-001 lives in a source file, so its definition line resolves and the
836+
// browser scroll target / VSIX line attribute must be present.
837+
assert!(
838+
body.contains("data-source-line=") && body.contains("scrollIntoView"),
839+
"artifact detail source link must target the artifact's definition line"
840+
);
841+
842+
child.kill().ok();
843+
child.wait().ok();
844+
}
845+
810846
// ── Embed resolution in documents ──────────────────────────────────────
811847

812848
/// The documents page should not contain any embed-error spans for valid

0 commit comments

Comments
 (0)