mirror of
https://github.com/alphaXiv/OpenResearch.git
synced 2026-11-11 09:59:34 +08:00
Tree cards + file viewer UX: Code shortcut, labels, line numbers
- Cards: labeled Changes/Logs/Code actions (Terminal renamed), GitHub-mark icon link at the card edge; width 240->264 so the row fits; no phantom selection outline or pointer cursor (selection state was write-only and is removed; RF nodes stay selectable so pointer-events keep the buttons alive, visuals suppressed in CSS) - Card Code button opens the project-level Code tab pinned to that experiment's branch (CodeTabDef.sessionId nullable); chat-header Code button removed along with the session-tab opener it fed - Changes/Code headers: BRANCH/COMMIT/SOURCE micro-labels; shared GitHubMark (lucide has no brand icons) used by pill, cards, and code-tab link - File viewer: sticky line-number gutter (1:1 with unwrapped rows) and a slimmer header - ui/dist rebuilt
This commit is contained in:
+108
-105
File diff suppressed because one or more lines are too long
Vendored
+32
File diff suppressed because one or more lines are too long
Vendored
-32
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -8,8 +8,8 @@
|
||||
html { background: #ffffff; }
|
||||
@media (prefers-color-scheme: dark) { html { background: #0e0c0c; } }
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/index-VwwcTCRX.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-hCeZGmkM.css">
|
||||
<script type="module" crossorigin src="/assets/index-BDvcb1RI.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DHqNmNnI.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
+20
-42
@@ -79,7 +79,9 @@ interface PlanViewDef {
|
||||
* `code` flag (the other tab kinds discriminate on `view`/`path`/`kind`). */
|
||||
interface CodeTabDef {
|
||||
code: true;
|
||||
sessionId: string;
|
||||
/** Owning chat session, or null for the project-level tab (opened from an
|
||||
* experiment card's Code shortcut — browses branches via the hub clone). */
|
||||
sessionId: string | null;
|
||||
/** Source to browse: "" = live session worktree, else a branch name. */
|
||||
sel: string;
|
||||
/** Whether `sel` was picked by the user — a seeded default silently falls
|
||||
@@ -89,14 +91,6 @@ interface CodeTabDef {
|
||||
toggled: ReadonlySet<string>;
|
||||
}
|
||||
|
||||
/** Branch of the experiment owning the project's most recent run — the code
|
||||
* the user most likely wants to browse — or "" (live worktree) without runs. */
|
||||
function defaultCodeSelection(experiments: Experiment[], runs: Run[]): string {
|
||||
if (runs.length === 0) return "";
|
||||
const latest = runs.reduce((a, b) => (b.createdAt > a.createdAt ? b : a));
|
||||
return experiments.find((e) => e.id === latest.experimentId)?.branchName ?? "";
|
||||
}
|
||||
|
||||
// Map a path an agent reported to a repo-relative one — keeping the session
|
||||
// id when it points into a per-session worktree, so the file is read from the
|
||||
// right checkout. Relative paths name files in the click context's checkout
|
||||
@@ -175,7 +169,6 @@ export default function App() {
|
||||
const [runs, setRuns] = useState<Run[]>([]);
|
||||
const [files, setFiles] = useState<ProjectFiles | null>(null);
|
||||
const [view, setView] = useState<"tree" | "table">("tree");
|
||||
const [selectedExpId, setSelectedExpId] = useState<string | null>(null);
|
||||
const [selectedRunId, setSelectedRunId] = useState<string | null>(null);
|
||||
// Right-panel tab strip: the pinned Experiments tab plus a closable tab per
|
||||
// opened experiment view / project file. Views are single-purpose, so the
|
||||
@@ -236,7 +229,6 @@ export default function App() {
|
||||
setExperiments([]);
|
||||
setRuns([]);
|
||||
setFiles(null);
|
||||
setSelectedExpId(null);
|
||||
setSelectedRunId(null);
|
||||
setExpTabs([]);
|
||||
setFileTabs([]);
|
||||
@@ -274,7 +266,6 @@ export default function App() {
|
||||
// Open an experiment view as a right-panel tab (creating it if needed) and
|
||||
// focus it.
|
||||
const openExperimentTab = useCallback((id: string, view: ExperimentView = "changes") => {
|
||||
setSelectedExpId(id);
|
||||
const tab = { id, view };
|
||||
setExpTabs((prev) => (prev.some((t) => sameExpTab(t, tab)) ? prev : [...prev, tab]));
|
||||
setRightTab(tab);
|
||||
@@ -350,34 +341,29 @@ export default function App() {
|
||||
[planTabs, rightTab],
|
||||
);
|
||||
|
||||
// Open the code browser for a chat session — live worktree or experiment
|
||||
// branches — one tab per session (deduped on sessionId), mirroring the
|
||||
// file/plan tab handlers. A new tab's source defaults to the latest-run
|
||||
// experiment's branch; reopening keeps the existing tab's state.
|
||||
const openCodeTab = useCallback(
|
||||
(sessionId: string) => {
|
||||
const existing = codeTabs.find((t) => t.sessionId === sessionId);
|
||||
const tab: CodeTabDef = existing ?? {
|
||||
code: true,
|
||||
sessionId,
|
||||
sel: defaultCodeSelection(experiments, runs),
|
||||
selPicked: false,
|
||||
toggled: new Set<string>(),
|
||||
};
|
||||
// Self-guarding updater: a double invocation must not push duplicates.
|
||||
// Card shortcut: browse a specific experiment branch in the project-level
|
||||
// code tab (sessionId null), pinning the pick so it never falls back.
|
||||
const openCodeTabForBranch = useCallback(
|
||||
(branch: string) => {
|
||||
const existing = codeTabs.find((t) => t.sessionId === null);
|
||||
const tab: CodeTabDef = existing
|
||||
? { ...existing, sel: branch, selPicked: true }
|
||||
: { code: true, sessionId: null, sel: branch, selPicked: true, toggled: new Set<string>() };
|
||||
setCodeTabs((prev) =>
|
||||
prev.some((t) => t.sessionId === sessionId) ? prev : [...prev, tab],
|
||||
prev.some((t) => t.sessionId === null)
|
||||
? prev.map((t) => (t.sessionId === null ? tab : t))
|
||||
: [...prev, tab],
|
||||
);
|
||||
setRightTab(tab);
|
||||
setPanelOpen(true);
|
||||
},
|
||||
[codeTabs, experiments, runs],
|
||||
[codeTabs],
|
||||
);
|
||||
|
||||
// Source/expansion changes persist on the tab def, not in CodeTab state —
|
||||
// the component unmounts whenever another right-pane tab fronts it.
|
||||
const updateCodeTab = useCallback(
|
||||
(sessionId: string, patch: Partial<Omit<CodeTabDef, "code" | "sessionId">>) => {
|
||||
(sessionId: string | null, patch: Partial<Omit<CodeTabDef, "code" | "sessionId">>) => {
|
||||
setCodeTabs((prev) => prev.map((t) => (t.sessionId === sessionId ? { ...t, ...patch } : t)));
|
||||
},
|
||||
[],
|
||||
@@ -526,7 +512,6 @@ export default function App() {
|
||||
}}
|
||||
onOpenFile={openFileTab}
|
||||
onOpenPlan={openPlanTab}
|
||||
onOpenCode={openCodeTab}
|
||||
>
|
||||
{mainView === "files" ? (
|
||||
(() => {
|
||||
@@ -573,10 +558,7 @@ export default function App() {
|
||||
<GitBranch size={12} style={{ flexShrink: 0 }} />
|
||||
)
|
||||
}
|
||||
onSelect={() => {
|
||||
setRightTab(t);
|
||||
setSelectedExpId(t.id);
|
||||
}}
|
||||
onSelect={() => setRightTab(t)}
|
||||
onClose={() => closeExperimentTab(t)}
|
||||
/>
|
||||
);
|
||||
@@ -603,7 +585,7 @@ export default function App() {
|
||||
))}
|
||||
{codeTabs.map((t) => (
|
||||
<ClosableTab
|
||||
key={`code:${t.sessionId}`}
|
||||
key={`code:${t.sessionId ?? "@project"}`}
|
||||
active={codeTab !== null && codeTab.sessionId === t.sessionId}
|
||||
label="Code"
|
||||
icon={<FolderTree size={12} style={{ flexShrink: 0 }} />}
|
||||
@@ -659,12 +641,8 @@ export default function App() {
|
||||
experiments={experiments}
|
||||
runs={runs}
|
||||
project={activeProject}
|
||||
selectedId={selectedExpId}
|
||||
onSelect={(id) => {
|
||||
setSelectedRunId(null);
|
||||
setSelectedExpId(id);
|
||||
}}
|
||||
onOpenView={openExperimentTab}
|
||||
onOpenCodeBranch={openCodeTabForBranch}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
@@ -715,7 +693,7 @@ export default function App() {
|
||||
const def = codeTabs.find((t) => t.sessionId === codeTab.sessionId) ?? codeTab;
|
||||
return (
|
||||
<CodeTab
|
||||
key={`code:${def.sessionId}`}
|
||||
key={`code:${def.sessionId ?? "@project"}`}
|
||||
projectId={projectId}
|
||||
sessionId={def.sessionId}
|
||||
project={activeProject}
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { githubBranchUrl } from "../api";
|
||||
|
||||
/** GitHub mark (lucide dropped brand icons). fill=currentColor so it inherits
|
||||
* hover styling like lucide siblings. */
|
||||
export function GitHubMark({ size }: { size: number }) {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
/** A branch name as a pill linking to its GitHub tree view (files-pill
|
||||
* styling). */
|
||||
export function BranchPill({
|
||||
@@ -21,7 +30,7 @@ export function BranchPill({
|
||||
title={`Open ${branch} on GitHub`}
|
||||
>
|
||||
<code>{branch}</code>
|
||||
<ExternalLink size={12} />
|
||||
<GitHubMark size={12} />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
CornerDownLeft,
|
||||
FlaskConical,
|
||||
FolderOpen,
|
||||
FolderTree,
|
||||
MoreHorizontal,
|
||||
PanelLeft,
|
||||
Plus,
|
||||
@@ -749,7 +748,6 @@ export function ChatPanel({
|
||||
onTogglePanel,
|
||||
onOpenFile,
|
||||
onOpenPlan,
|
||||
onOpenCode,
|
||||
children,
|
||||
}: {
|
||||
projectId: string;
|
||||
@@ -773,9 +771,6 @@ export function ChatPanel({
|
||||
onOpenFile?: (path: string, sessionId?: string) => void;
|
||||
/** Open a plan's markdown as a right-pane tab (plan strip / plan cards). */
|
||||
onOpenPlan?: (plan: string, sessionId: string, promptId: string) => void;
|
||||
/** Open the code browser (live session worktree / experiment branches) as
|
||||
* a right-pane tab for the active session. */
|
||||
onOpenCode?: (sessionId: string) => void;
|
||||
/** Middle-pane content when a settings section is active (the SettingsView). */
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
@@ -1277,16 +1272,6 @@ export function ChatPanel({
|
||||
>
|
||||
{activeSession ? activeSession.title?.trim() || "Untitled" : "New session"}
|
||||
</div>
|
||||
{onOpenCode && activeId && (
|
||||
<button
|
||||
className="icon-btn"
|
||||
title="Code"
|
||||
aria-label="Code"
|
||||
onClick={() => onOpenCode(activeId)}
|
||||
>
|
||||
<FolderTree size={15} />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className={`icon-btn ${panelOpen ? "active" : ""}`}
|
||||
title="Experiments"
|
||||
|
||||
@@ -180,7 +180,8 @@ export function CodeTab({
|
||||
}: {
|
||||
projectId: string;
|
||||
/** Chat session whose worktree the live view browses. */
|
||||
sessionId: string;
|
||||
/** null: project-level tab (card shortcut) — no live-worktree source. */
|
||||
sessionId: string | null;
|
||||
/** Owning project — supplies owner/repo for the GitHub branch link. */
|
||||
project: Project;
|
||||
/** Project experiments — one selectable branch entry each (deduped). */
|
||||
@@ -218,7 +219,7 @@ export function CodeTab({
|
||||
const id = ++reqId.current;
|
||||
inFlight.current = true;
|
||||
if (showSpinner) setLoading(true);
|
||||
getCodeTree(projectId, sel ? { ref: sel } : { sessionId })
|
||||
getCodeTree(projectId, sel ? { ref: sel } : { sessionId: sessionId ?? undefined })
|
||||
.then((d) => {
|
||||
if (id !== reqId.current) return;
|
||||
setData(d);
|
||||
@@ -301,13 +302,16 @@ export function CodeTab({
|
||||
return (
|
||||
<div className="code-tab">
|
||||
<div className="code-tab-header">
|
||||
<span className="ctl-label">Source</span>
|
||||
<select
|
||||
className="input sm code-tab-select"
|
||||
value={sel}
|
||||
onChange={(e) => onSelChange(e.target.value, true)}
|
||||
title="Source to browse"
|
||||
>
|
||||
<option value="">live — session worktree</option>
|
||||
<option value="">
|
||||
{sessionId ? "live — session worktree" : "project clone"}
|
||||
</option>
|
||||
{branchOptions.map((o) => (
|
||||
<option key={o.branch} value={o.branch}>
|
||||
{o.label}
|
||||
@@ -335,7 +339,7 @@ export function CodeTab({
|
||||
{loading ? <span className="spinner" /> : <RotateCw size={13} />}
|
||||
</button>
|
||||
</div>
|
||||
{!sel && data?.root === "clone" && (
|
||||
{sessionId !== null && !sel && data?.root === "clone" && (
|
||||
<div className="code-tab-note">
|
||||
This session's worktree isn't available — showing the project clone.
|
||||
</div>
|
||||
@@ -355,7 +359,7 @@ export function CodeTab({
|
||||
depth={0}
|
||||
toggled={toggled}
|
||||
onToggle={toggle}
|
||||
onOpenFile={(path) => onOpenFile(path, sessionId, sel || undefined)}
|
||||
onOpenFile={(path) => onOpenFile(path, sessionId ?? undefined, sel || undefined)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -281,6 +281,7 @@ function ChangesView({ experiment, project }: { experiment: Experiment; project:
|
||||
<div className="drawer-body">
|
||||
<div className="drawer-section">
|
||||
<div className="changes-branch">
|
||||
<span className="ctl-label">Branch</span>
|
||||
<BranchPill
|
||||
owner={project.githubOwner}
|
||||
repo={project.githubRepo}
|
||||
@@ -298,6 +299,7 @@ function ChangesView({ experiment, project }: { experiment: Experiment; project:
|
||||
) : (
|
||||
<>
|
||||
<div className="commit-picker">
|
||||
<span className="ctl-label">Commit</span>
|
||||
{selection === UNCOMMITTED && <span className="uncommitted-dot" />}
|
||||
<select
|
||||
className="input sm"
|
||||
|
||||
@@ -64,6 +64,13 @@ export function FileViewer({
|
||||
[data, path],
|
||||
);
|
||||
|
||||
// One number per source line; a trailing newline ends a line, it doesn't
|
||||
// start an empty one.
|
||||
const lineCount =
|
||||
data && !data.notFound && data.content
|
||||
? data.content.split("\n").length - (data.content.endsWith("\n") ? 1 : 0)
|
||||
: 0;
|
||||
|
||||
const notFoundCopy = (d: ProjectFile) => {
|
||||
if (gitRef) return `File not found on branch ${gitRef}.`;
|
||||
if (sessionId && d.root === "clone")
|
||||
@@ -124,9 +131,14 @@ export function FileViewer({
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<pre className="file-view-code">
|
||||
<code>{rendered}</code>
|
||||
</pre>
|
||||
<div className="file-view-codewrap">
|
||||
<pre className="file-view-gutter" aria-hidden="true">
|
||||
{Array.from({ length: lineCount }, (_, i) => i + 1).join("\n")}
|
||||
</pre>
|
||||
<pre className="file-view-code">
|
||||
<code>{rendered}</code>
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
{data.truncated && (
|
||||
<div className="file-view-note">File truncated — showing the first 512 KB.</div>
|
||||
|
||||
@@ -8,13 +8,14 @@ import {
|
||||
type Node,
|
||||
type NodeProps,
|
||||
} from "@xyflow/react";
|
||||
import { ExternalLink, GitBranch, Terminal } from "lucide-react";
|
||||
import { FolderTree, GitBranch, Terminal } from "lucide-react";
|
||||
import { GitHubMark } from "./BranchPill";
|
||||
import { memo, useMemo } from "react";
|
||||
import { githubBranchUrl, timeAgo, type Experiment, type Project, type Run } from "../api";
|
||||
import type { ExperimentView } from "./DetailDrawer";
|
||||
import { StatusBadge } from "./StatusBadge";
|
||||
|
||||
const NODE_W = 240;
|
||||
const NODE_W = 264;
|
||||
const NODE_H = 132;
|
||||
const GAP_X = 44;
|
||||
const GAP_Y = 72;
|
||||
@@ -25,10 +26,10 @@ type ExpNodeData = {
|
||||
latestRun: Run | null;
|
||||
runs: Run[]; // oldest → newest
|
||||
isBaseline: boolean;
|
||||
selected: boolean;
|
||||
githubOwner: string;
|
||||
githubRepo: string;
|
||||
onOpenView: (id: string, view: ExperimentView) => void;
|
||||
onOpenCodeBranch: (branch: string) => void;
|
||||
};
|
||||
type ExpFlowNode = Node<ExpNodeData, "exp">;
|
||||
|
||||
@@ -71,13 +72,13 @@ function runSquareClass(status: string): string {
|
||||
}
|
||||
|
||||
const ExpNode = memo(function ExpNode({ data }: NodeProps<ExpFlowNode>) {
|
||||
const { exp, latestRun, runs, isBaseline, selected, githubOwner, githubRepo, onOpenView } = data;
|
||||
const { exp, latestRun, runs, isBaseline, githubOwner, githubRepo, onOpenView, onOpenCodeBranch } = data;
|
||||
const status = latestRun?.status;
|
||||
const live = status === "running" || status === "starting";
|
||||
const kind = isBaseline ? "BASELINE" : live ? "RUNNING" : "EXPERIMENT";
|
||||
const squares = runs.slice(-MAX_SQUARES);
|
||||
return (
|
||||
<div className={`exp-node ${selected ? "selected" : ""} ${live ? "live" : ""}`}>
|
||||
<div className={`exp-node ${live ? "live" : ""}`}>
|
||||
<Handle type="target" position={Position.Top} />
|
||||
<div className="node-eyebrow">
|
||||
<span>{kind}</span>
|
||||
@@ -120,19 +121,28 @@ const ExpNode = memo(function ExpNode({ data }: NodeProps<ExpFlowNode>) {
|
||||
onClick={() => onOpenView(exp.id, "terminal")}
|
||||
>
|
||||
<Terminal size={13} />
|
||||
Terminal
|
||||
Logs
|
||||
</button>
|
||||
)}
|
||||
<a
|
||||
<button
|
||||
className="node-action"
|
||||
title={`Browse code on ${exp.branchName}`}
|
||||
onClick={() => onOpenCodeBranch(exp.branchName)}
|
||||
>
|
||||
<FolderTree size={13} />
|
||||
Code
|
||||
</button>
|
||||
{/* Icon-only: labeled actions + the link overflow the card's fixed width. */}
|
||||
<a
|
||||
className="node-action node-action-ext"
|
||||
title={`Open ${exp.branchName} on GitHub`}
|
||||
aria-label={`Open ${exp.branchName} on GitHub`}
|
||||
href={githubBranchUrl(githubOwner, githubRepo, exp.branchName)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<ExternalLink size={13} />
|
||||
GitHub
|
||||
<GitHubMark size={13} />
|
||||
</a>
|
||||
</div>
|
||||
<Handle type="source" position={Position.Bottom} />
|
||||
@@ -151,18 +161,17 @@ export function TreeView({
|
||||
experiments,
|
||||
runs,
|
||||
project,
|
||||
selectedId,
|
||||
onSelect,
|
||||
onOpenView,
|
||||
onOpenCodeBranch,
|
||||
}: {
|
||||
experiments: Experiment[];
|
||||
runs: Run[];
|
||||
/** Owning project — supplies owner/repo for the GitHub branch links. */
|
||||
project: Project;
|
||||
selectedId: string | null;
|
||||
onSelect: (id: string | null) => void;
|
||||
/** Open an experiment view as a right-pane tab (card shortcut buttons). */
|
||||
onOpenView: (id: string, view: ExperimentView) => void;
|
||||
/** Browse an experiment branch's code in the project-level Code tab. */
|
||||
onOpenCodeBranch: (branch: string) => void;
|
||||
}) {
|
||||
const { nodes, edges } = useMemo(() => {
|
||||
const runsByExp = new Map<string, Run[]>();
|
||||
@@ -188,10 +197,10 @@ export function TreeView({
|
||||
latestRun: expRuns[expRuns.length - 1] ?? null,
|
||||
runs: expRuns,
|
||||
isBaseline: !node.exp.parentExperimentId,
|
||||
selected: node.exp.id === selectedId,
|
||||
githubOwner: project.githubOwner,
|
||||
githubRepo: project.githubRepo,
|
||||
onOpenView,
|
||||
onOpenCodeBranch,
|
||||
},
|
||||
});
|
||||
if (node.children.length === 0) return;
|
||||
@@ -218,7 +227,7 @@ export function TreeView({
|
||||
rx += w + GAP_X;
|
||||
}
|
||||
return { nodes, edges };
|
||||
}, [experiments, runs, selectedId, onOpenView, project.githubOwner, project.githubRepo]);
|
||||
}, [experiments, runs, onOpenView, onOpenCodeBranch, project.githubOwner, project.githubRepo]);
|
||||
|
||||
if (experiments.length === 0) {
|
||||
return (
|
||||
@@ -240,8 +249,6 @@ export function TreeView({
|
||||
minZoom={0.15}
|
||||
fitView
|
||||
fitViewOptions={{ padding: 0.25, maxZoom: 1 }}
|
||||
onNodeClick={(_, node) => onSelect(node.id)}
|
||||
onPaneClick={() => onSelect(null)}
|
||||
>
|
||||
<Background variant={BackgroundVariant.Dots} color="var(--dots-strong)" gap={28} size={1.6} />
|
||||
</ReactFlow>
|
||||
|
||||
+60
-9
@@ -1411,12 +1411,25 @@ textarea::placeholder {
|
||||
|
||||
/* ---- tree nodes (graph cards on the dotted canvas) ---- */
|
||||
|
||||
/* Cards must stay `selectable` (ReactFlow sets pointer-events:none on nodes
|
||||
* that are neither draggable nor selectable, which would kill the action
|
||||
* buttons) — but selection itself means nothing here, so suppress its
|
||||
* pointer cursor and click outline. Specificity beats RF's stylesheet. */
|
||||
.react-flow__node.react-flow__node-exp.selectable {
|
||||
cursor: default;
|
||||
}
|
||||
.react-flow__node.react-flow__node-exp.selectable.selected,
|
||||
.react-flow__node.react-flow__node-exp.selectable:focus,
|
||||
.react-flow__node.react-flow__node-exp.selectable:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.exp-node {
|
||||
width: 240px;
|
||||
/* Keep in sync with NODE_W in TreeView.tsx (layout math uses it). */
|
||||
width: 264px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--base);
|
||||
padding: 10px 12px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
font-size: 12.5px;
|
||||
transition: box-shadow 120ms ease;
|
||||
@@ -1424,10 +1437,6 @@ textarea::placeholder {
|
||||
.exp-node:hover {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.exp-node.selected {
|
||||
border-color: var(--text);
|
||||
box-shadow: 0 0 0 1px var(--text);
|
||||
}
|
||||
.exp-node.live {
|
||||
border-color: var(--accent-teal);
|
||||
box-shadow: 0 2px 12px rgba(32, 154, 132, 0.2);
|
||||
@@ -1505,13 +1514,14 @@ textarea::placeholder {
|
||||
border-top: 1px solid var(--border-variant);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
gap: 3px;
|
||||
}
|
||||
.exp-node .node-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 3px 8px;
|
||||
/* Tight: Changes + Terminal + Code + the GitHub icon share one fixed-width row. */
|
||||
padding: 3px 6px;
|
||||
font-size: 10.5px;
|
||||
font-weight: 700;
|
||||
color: var(--subtext);
|
||||
@@ -1523,6 +1533,11 @@ textarea::placeholder {
|
||||
color: var(--text);
|
||||
background: var(--surface);
|
||||
}
|
||||
/* External GitHub shortcut: icon-only, pinned to the card's far edge. */
|
||||
.exp-node .node-action-ext {
|
||||
margin-left: auto;
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
||||
/* run-history squares (RUNS strip on node cards) */
|
||||
.run-squares {
|
||||
@@ -1874,6 +1889,8 @@ textarea::placeholder {
|
||||
/* commit picker (Changes tab) */
|
||||
.changes-branch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.commit-picker {
|
||||
@@ -1881,6 +1898,17 @@ textarea::placeholder {
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
/* Tiny caption naming what a header control is (Branch / Commit / Source) —
|
||||
* two adjacent unlabeled monospace pickers are indistinguishable. */
|
||||
.ctl-label {
|
||||
flex-shrink: 0;
|
||||
font-family: var(--mono);
|
||||
font-size: 9.5px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
.commit-picker select {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
@@ -3361,7 +3389,7 @@ button.files-pill-link:hover {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 14px;
|
||||
padding: 4px 12px;
|
||||
border-bottom: 1px solid var(--border-variant);
|
||||
color: var(--subtext);
|
||||
flex-shrink: 0;
|
||||
@@ -3398,6 +3426,29 @@ button.files-pill-link:hover {
|
||||
overflow: auto;
|
||||
background: var(--base);
|
||||
}
|
||||
/* Gutter + code share one row so line numbers track content rows 1:1 — the
|
||||
code never soft-wraps (min-width: max-content), so line N is row N. */
|
||||
.file-view-codewrap {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
min-width: max-content;
|
||||
}
|
||||
.file-view-gutter {
|
||||
margin: 0;
|
||||
padding: 14px 10px 14px 14px;
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
text-align: right;
|
||||
color: var(--muted);
|
||||
user-select: none;
|
||||
/* Stay visible while the code scrolls horizontally. */
|
||||
position: sticky;
|
||||
left: 0;
|
||||
background: var(--base);
|
||||
border-right: 1px solid var(--border-variant);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.file-view-code {
|
||||
margin: 0;
|
||||
padding: 14px 16px;
|
||||
|
||||
Reference in New Issue
Block a user