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
37 changes: 37 additions & 0 deletions rivet-cli/src/serve/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,40 @@ document.addEventListener('DOMContentLoaded',function(){{mermaid.run({{querySele
CSS = styles::CSS,
))
}

/// Embed layout — no sidebar, no context bar, just content with HTMX.
/// Used when the dashboard is embedded in VS Code WebView.
pub(crate) fn embed_layout(content: &str, _state: &AppState) -> Html<String> {
let version = env!("CARGO_PKG_VERSION");
Html(format!(
r##"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Rivet</title>
<style>{FONTS_CSS}{CSS}</style>
<style>
body {{ background: var(--bg); color: var(--text); margin: 0; }}
main {{ padding: 1rem 1.5rem; max-width: 100%; }}
</style>
<script src="/assets/htmx.js"></script>
<script src="/assets/mermaid.js"></script>
<script>
mermaid.initialize({{startOnLoad:false,theme:'neutral',securityLevel:'strict'}});
function renderMermaid(){{mermaid.run({{querySelector:'.mermaid'}}).catch(function(){{}})}}
document.addEventListener('htmx:afterSwap',renderMermaid);
document.addEventListener('DOMContentLoaded',renderMermaid);
</script>
</head>
<body>
<main id="content">
{content}
</main>
<div style="padding:.5rem 1.5rem;font-size:.75rem;color:var(--text-secondary)">Rivet v{version}</div>
</body>
</html>"##,
FONTS_CSS = styles::FONTS_CSS,
CSS = styles::CSS,
))
}
8 changes: 6 additions & 2 deletions rivet-cli/src/serve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -595,15 +595,16 @@ async fn wrap_full_page(
let query = req.uri().query().unwrap_or("").to_string();
let is_htmx = req.headers().contains_key("hx-request");
let is_print = query.contains("print=1");
let is_embed = query.contains("embed=1");
let method = req.method().clone();

let response = next.run(req).await;

// Only wrap GET requests to view routes (not assets or APIs)
// For "/" without print=1, the index handler already renders the full page.
// For "/" without print/embed, the index handler already renders the full page.
if method == axum::http::Method::GET
&& !is_htmx
&& (path != "/" || is_print)
&& (path != "/" || is_print || is_embed)
&& !path.starts_with("/api/")
&& !path.starts_with("/assets/")
&& !path.starts_with("/wasm/")
Expand All @@ -618,6 +619,9 @@ async fn wrap_full_page(
if is_print {
return layout::print_layout(&content, &app).into_response();
}
if is_embed {
return layout::embed_layout(&content, &app).into_response();
}
return layout::page_layout(&content, &app).into_response();
}

Expand Down
17 changes: 17 additions & 0 deletions vscode-rivet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,23 @@
],
"main": "./out/extension",
"contributes": {
"viewsContainers": {
"activitybar": [
{
"id": "rivet",
"title": "Rivet SDLC",
"icon": "icon.svg"
}
]
},
"views": {
"rivet": [
{
"id": "rivetExplorer",
"name": "Explorer"
}
]
},
"commands": [
{
"command": "rivet.showDashboard",
Expand Down
57 changes: 56 additions & 1 deletion vscode-rivet/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ export async function activate(context: vscode.ExtensionContext) {
vscode.commands.registerCommand('rivet.showSTPA', () => showDashboard(context, '/stpa')),
vscode.commands.registerCommand('rivet.validate', () => runValidate()),
vscode.commands.registerCommand('rivet.addArtifact', () => addArtifact()),
vscode.commands.registerCommand('rivet.navigateTo', (urlPath: string) => showDashboard(context, urlPath)),
);

// --- Sidebar Tree View ---
const treeProvider = new RivetTreeProvider();
vscode.window.registerTreeDataProvider('rivetExplorer', treeProvider);
context.subscriptions.push(
vscode.commands.registerCommand('rivet.refreshTree', () => treeProvider.refresh()),
);

// --- Status Bar ---
Expand Down Expand Up @@ -178,7 +186,9 @@ async function showDashboard(context: vscode.ExtensionContext, urlPath: string =
}

// Map localhost to a VS Code-accessible URI (works in WebViews)
const localUri = vscode.Uri.parse(`http://127.0.0.1:${dashboardPort}${urlPath}`);
// ?embed=1 strips the sidebar (VS Code tree view handles navigation)
const sep = urlPath.includes('?') ? '&' : '?';
const localUri = vscode.Uri.parse(`http://127.0.0.1:${dashboardPort}${urlPath}${sep}embed=1`);
const mappedUri = await vscode.env.asExternalUri(localUri);

if (dashboardPanel) {
Expand Down Expand Up @@ -288,3 +298,48 @@ async function addArtifact() {
vscode.window.showErrorMessage(`Failed to add artifact: ${msg}`);
}
}

// --- Sidebar Tree View ---

class RivetTreeProvider implements vscode.TreeDataProvider<RivetTreeItem> {
private _onDidChangeTreeData = new vscode.EventEmitter<void>();
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;

refresh(): void {
this._onDidChangeTreeData.fire();
}

getTreeItem(element: RivetTreeItem): vscode.TreeItem {
return element;
}

getChildren(element?: RivetTreeItem): RivetTreeItem[] {
if (element) return [];

return [
new RivetTreeItem('Stats', '/stats', 'dashboard'),
new RivetTreeItem('Artifacts', '/artifacts', 'symbol-class'),
new RivetTreeItem('Validation', '/validate', 'pass'),
new RivetTreeItem('STPA', '/stpa', 'shield'),
new RivetTreeItem('Graph', '/graph', 'type-hierarchy'),
new RivetTreeItem('Documents', '/documents', 'book'),
new RivetTreeItem('Matrix', '/matrix', 'table'),
new RivetTreeItem('Coverage', '/coverage', 'checklist'),
new RivetTreeItem('Source', '/source', 'code'),
new RivetTreeItem('Results', '/results', 'beaker'),
new RivetTreeItem('Help', '/help', 'question'),
];
}
}

class RivetTreeItem extends vscode.TreeItem {
constructor(label: string, public readonly urlPath: string, icon: string) {
super(label, vscode.TreeItemCollapsibleState.None);
this.iconPath = new vscode.ThemeIcon(icon);
this.command = {
command: 'rivet.navigateTo',
title: label,
arguments: [urlPath],
};
}
}
Loading