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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,9 @@ test-assets: test-api-contract
npm --prefix $(WEB_DIR) run lint
npm --prefix $(WEB_DIR) run typecheck
npm --prefix $(WEB_DIR) run build
@if [ -n "$$(git status --porcelain -- crates/$(PKG)/assets/dist $(WEB_DIR)/packages/nac-client/dist)" ]; then \
@if [ -n "$$(git status --porcelain -- crates/$(PKG)/assets/dist $(WEB_DIR)/packages/nac-client/dist $(WEB_DIR)/packages/nac-presentation/dist)" ]; then \
printf '%s\n' "error: committed web/client build output is stale; rebuild and commit it"; \
git status --porcelain -- crates/$(PKG)/assets/dist $(WEB_DIR)/packages/nac-client/dist; \
git status --porcelain -- crates/$(PKG)/assets/dist $(WEB_DIR)/packages/nac-client/dist $(WEB_DIR)/packages/nac-presentation/dist; \
exit 1; \
fi

Expand Down
74 changes: 74 additions & 0 deletions crates/nac-server/assets/dist/assets/index-B7MngmAP.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions crates/nac-server/assets/dist/assets/index-Cob58tfW.css

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions crates/nac-server/assets/dist/assets/index-UR88_-EG.css

This file was deleted.

74 changes: 0 additions & 74 deletions crates/nac-server/assets/dist/assets/index-tIk9oWAm.js

This file was deleted.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

This file was deleted.

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions crates/nac-server/assets/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
serves only `/assets/*`, so an icon at the dist root would 404. -->
<link rel="icon" type="image/svg+xml" href="/assets/dist/assets/favicon-BxL16L46.svg" />
<title>NAC</title>
<script type="module" crossorigin src="/assets/dist/assets/index-tIk9oWAm.js"></script>
<script type="module" crossorigin src="/assets/dist/assets/index-B7MngmAP.js"></script>
<link rel="modulepreload" crossorigin href="/assets/dist/assets/rolldown-runtime-hePW80VL.js">
<link rel="stylesheet" crossorigin href="/assets/dist/assets/index-UR88_-EG.css">
<link rel="stylesheet" crossorigin href="/assets/dist/assets/index-Cob58tfW.css">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
9 changes: 9 additions & 0 deletions crates/nac-server/tests/source_binary_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ use std::io::{Read, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Output, Stdio};
use std::sync::Mutex;
use std::time::{Duration, SystemTime, UNIX_EPOCH};

const BINARY: &str = env!("CARGO_BIN_EXE_nac-web");

// A concurrent fork can inherit the writable descriptor from fs::copy until
// exec closes it, causing ETXTBSY when another test executes its new copy.
// Serialize every test in this process that copies or launches a binary.
static BINARY_EXECUTION: Mutex<()> = Mutex::new(());

struct ChildGuard(Option<Child>);

impl ChildGuard {
Expand Down Expand Up @@ -73,6 +79,7 @@ fn http_body(address: SocketAddr, path: &str) -> std::io::Result<String> {

#[test]
fn unset_track_custom_install_reports_dev_identity_and_uses_dev_store() {
let _binary_execution = BINARY_EXECUTION.lock().unwrap();
assert_eq!(env!("NAC_BUILD_TRACK"), "dev");
assert_eq!(
env!("NAC_BUILD_ID"),
Expand Down Expand Up @@ -155,6 +162,7 @@ fn unset_track_custom_install_reports_dev_identity_and_uses_dev_store() {

#[test]
fn custom_dev_upgrade_refuses_before_network_or_install_mutation() {
let _binary_execution = BINARY_EXECUTION.lock().unwrap();
assert_eq!(env!("NAC_BUILD_TRACK"), "dev");
let root = temp_root("upgrade");
std::fs::create_dir_all(&root).unwrap();
Expand Down Expand Up @@ -183,6 +191,7 @@ fn custom_dev_upgrade_refuses_before_network_or_install_mutation() {
#[cfg(not(feature = "managed-fault-fixture"))]
#[test]
fn normal_binary_rejects_private_fault_controls_before_startup() {
let _binary_execution = BINARY_EXECUTION.lock().unwrap();
let root = temp_root("fault_exclusion");
let output = Command::new(BINARY)
.arg("--help")
Expand Down
3 changes: 2 additions & 1 deletion crates/nac-server/web/.oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"openapi.json",
"packages/nac-client/dist/**",
"src/app/atoms/file-icon/manifest.generated.ts",
"src/app/atoms/file-icon/icons/**"
"src/app/atoms/file-icon/icons/**",
"packages/nac-presentation/dist/**"
]
}
18 changes: 17 additions & 1 deletion crates/nac-server/web/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ server remains the source of business truth and wire schemas.
caching, cancellation, invalidation, and polling belong to query owners, not
presentational components.
- `packages/nac-client/` owns the runtime-free ALL-121 typed HTTP/SSE boundary.
Its full resource facade and generated operation classification share the
existing native methods; private control routes stay in the web adapter.
The standalone service modules are compatibility facades; do not fork its
transport, replay, reconnect, or command-admission logic back into the app.
- `src/app/types/openapi.generated.ts` is generated from Rust/OpenAPI. Import
Expand All @@ -24,6 +26,11 @@ server remains the source of business truth and wire schemas.
query invalidation, accessibility labels, and behavior-selection defaults.
- Generic providers are for genuinely cross-feature browser state. Do not use
them as a home for one feature's workflow.
- `src/app/runtime/` owns one immutable consumer binding, private query cache,
presentation stores and request/stream lifetime. Production feature owners
read that context; compatibility globals are only for legacy hook fixtures.
Replace the runtime when any binding changes. Keep caller routing, styles,
theme and authenticated stream construction explicit at the presentation root.

## Starting points

Expand Down Expand Up @@ -54,6 +61,10 @@ server remains the source of business truth and wire schemas.
- `components/inspector/ThreadsView.tsx` keeps orchestrator thread/workset
navigation and episode rendering together. Direct-child and managed-host
workflows remain separate features.
- `store/runtimeStore.ts` keeps snapshot hydration and ordered live-event
reduction in one store factory. Its activation fence and coupled run/tool/
transcript projection must share a lifetime; splitting the reduction across
independent stores would obscure ordering and late-event protection.

These existing UI owners may exceed 800 lines because splitting their tightly
coupled local form/view state would scatter one workflow. Do not add unrelated
Expand All @@ -80,7 +91,12 @@ make test-e2e
`packages/nac-client/src/`. The generator fails on unsupported schema
constructs; extend it explicitly rather than widening to `any`.
- `npm ... run build` writes `../assets/dist` and
`packages/nac-client/dist`. Commit source and both outputs together.
`packages/nac-client/dist` and `packages/nac-presentation/dist`. Commit
source and all outputs together. `vite.presentation.config.ts` reuses the
native entry and build pipeline; `scripts/presentation-css.ts` scopes the
generated stylesheet, and `scripts/presentation-types.mjs` removes source
aliases from emitted declarations. Keep React peers external and CSS/assets
opt-in; do not create a second UI or transport implementation.
Do not edit hashed assets by hand.
- `scripts/sync-file-icons.mjs` is the writer for synchronized icon assets when
that source set changes.
Expand Down
12 changes: 12 additions & 0 deletions crates/nac-server/web/e2e/fixtures/hosted.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Native hosted fixture</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./hosted.tsx"></script>
</body>
</html>
66 changes: 66 additions & 0 deletions crates/nac-server/web/e2e/fixtures/hosted.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Test-only caller of the production native presentation; no alternate feature UI.
import { StrictMode, useState } from "react";
import { createRoot } from "react-dom/client";
import { MemoryRouter } from "react-router-dom";
import { createNativeRuntime, NativePresentationRoot } from "../../src/app/runtime";
import { createNacClient } from "../../src/app/services/nacClient";
import "../../src/index.css";

const configuration = (await (await fetch("/fixture-config")).json()) as { sessionId: string };
function openRuntime(release: string) {
return createNativeRuntime({
scope: {
owner: "fixture-owner",
profile: "fixture-profile",
organization: "fixture-org",
host: "fixture-host",
incarnation: "fixture-incarnation",
endpoint: "/fixture-runtime",
release,
},
client: createNacClient({
endpoint: "/fixture-runtime",
credentials: "omit",
headers: () => ({ "X-Fixture-Auth": "fixture-only" }),
}),
eventSource: (url, init, context) => {
if (context.headers["x-fixture-auth"] !== "fixture-only")
throw new Error("missing caller stream context");
// The fixture server supplies a local test cookie to native EventSource.
return new EventSource(url, init);
},
});
}
function HostedFixture() {
const [runtime, setRuntime] = useState(() => openRuntime("first"));
return (
<>
<nav aria-label="Fixture controls">
<button
onClick={() => {
runtime.close();
setRuntime(openRuntime("replacement"));
}}
>
Replace runtime
</button>
<button onClick={() => runtime.close()}>Close runtime</button>
</nav>
<div style={{ height: "calc(100vh - 24px)" }}>
<NativePresentationRoot
runtime={runtime}
router={(children) => (
<MemoryRouter initialEntries={[`/session/${configuration.sessionId}/files`]}>
{children}
</MemoryRouter>
)}
/>
</div>
</>
);
}
createRoot(document.getElementById("root")!).render(
<StrictMode>
<HostedFixture />
</StrictMode>,
);
Loading
Loading