Skip to content

Commit ee245bb

Browse files
authored
ENG-1605: Initial skeleton website/layout (#934)
* [ENG-1605] Add /extract-nodes skeleton page Non-functional skeleton matching the mockup for the extract-nodes tool. Uses (extract) route group with own root layout, two-panel Sidebar + MainContent. All data hardcoded — no wiring or functionality. * Remove @repo/ui/globals.css import from extract layout The border-border class from shadcn globals.css is not defined in the extract layout's Tailwind context. The (home) layout doesn't import it either — not needed for this skeleton. * Address review: remove nested html/body, align tab counts - Remove <html> and <body> from (extract) layout — root layout already provides these. Matches (home) layout pattern. - Align TABS counts and footer text with actual SAMPLE_NODES data (6 items: 3 CLM + 3 EVD). * Rename sectionLabelClass to SECTION_LABEL_CLASS * Use shadcn components and correct node types from ontology - Add shadcn Button, Badge, Checkbox to @repo/ui - Rewrite Sidebar: use Checkbox, Button; node types now match discourse graph ontology (CLM, QUE, HYP, EVD, RES, SRC, THE) with candidateTag field per ENG-1603 spec shape - Rewrite MainContent: use Badge for type labels, Checkbox for selection, Button for actions - Remove prototype node types (EXP, MTD, PAT, CON, ISS, ART) that don't exist in the ontology * Fix eslint and prettier for shadcn components Align shadcn-generated code with repo conventions: interface → type, function → arrow, prettier formatting. * Colocate data in components, use full contract shapes Sidebar: NODE_TYPES with all ENG-1603 fields (label, description, candidateTag, color) colocated in component. MainContent: SAMPLE_NODES with all ENG-1601 fields (nodeType, content, supportSnippet, sourceSection) colocated in component. supportSnippet shown inline. Colors looked up from nodeType. Removed sampleData.ts — shared types will be a Zod schema later. * All types checked, sample for each type, 2 cards expanded - All 7 node types checked in sidebar - Sample extracted nodes for every type (claim, evidence, question, hypothesis, result, source, theory) - First 2 cards show expanded supportSnippet, rest collapsed - Buttons use explicit dark gradient instead of shadcn theme * Restore shadcn Button with custom color overrides Use Button from @repo/ui with className overrides for slate-900 dark theme instead of raw HTML buttons. * Use shadcn everywhere, colored pills only on result cards Sidebar: shadcn Checkbox replaces custom colored checkboxes. MainContent: Card+CardContent for result cards, Badge for filter tabs, Checkbox for selection, Button variant=ghost for show/hide details. Colored inline style only on the nodeType pill in each result card. * Remove node type descriptions from sidebar, align checkbox rows * Add explicit return types to extract route components * Remove unnecessary eslint naming-convention disables PascalCase for arrow-function variables has been allowed since ENG-643.
1 parent c316fe9 commit ee245bb

File tree

9 files changed

+678
-1
lines changed

9 files changed

+678
-1
lines changed
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
import { Badge } from "@repo/ui/components/ui/badge";
2+
import { Button } from "@repo/ui/components/ui/button";
3+
import { Card, CardContent } from "@repo/ui/components/ui/card";
4+
import { Checkbox } from "@repo/ui/components/ui/checkbox";
5+
import { Copy } from "lucide-react";
6+
7+
const NODE_TYPE_COLORS: Record<string, string> = {
8+
claim: "#7DA13E",
9+
question: "#99890E",
10+
hypothesis: "#7C4DFF",
11+
evidence: "#dc0c4a",
12+
result: "#E6A23C",
13+
source: "#9E9E9E",
14+
theory: "#8B5CF6",
15+
};
16+
17+
const SAMPLE_NODES = [
18+
{
19+
nodeType: "claim",
20+
content:
21+
"Basolateral secretion of Wnt5a is essential for establishing apical-basal polarity in epithelial cells.",
22+
supportSnippet:
23+
'"Wnt5a secreted from the basolateral surface was both necessary and sufficient for the establishment of apical-basal polarity" (p.9)',
24+
sourceSection: "Discussion",
25+
},
26+
{
27+
nodeType: "evidence",
28+
content:
29+
"Wnt5a was detected exclusively in the basolateral medium of polarized MDCK cells grown on Transwell filters, with no detectable signal in the apical fraction.",
30+
supportSnippet:
31+
'"Western blot analysis of conditioned media showed Wnt5a protein exclusively in the basolateral fraction (Fig. 2A, lanes 3-4)"',
32+
sourceSection: "Results",
33+
},
34+
{
35+
nodeType: "question",
36+
content:
37+
"What is the mechanism by which Wnt5a polarized secretion is directed to the basolateral membrane?",
38+
supportSnippet:
39+
'"The mechanism that directs Wnt5a specifically to the basolateral surface remains an open question" (p.11)',
40+
sourceSection: "Discussion",
41+
},
42+
{
43+
nodeType: "hypothesis",
44+
content:
45+
"Ror2 receptor activation at the basolateral surface mediates Wnt5a-dependent lumen positioning.",
46+
supportSnippet:
47+
'"We hypothesize that Ror2, as the primary receptor for Wnt5a at the basolateral membrane, transduces the polarity signal required for single-lumen formation"',
48+
sourceSection: "Discussion",
49+
},
50+
{
51+
nodeType: "result",
52+
content:
53+
"shRNA-mediated knockdown of Wnt5a resulted in multi-lumen cysts in 68% of colonies compared to 12% in control conditions.",
54+
supportSnippet:
55+
'"Quantification of cyst morphology revealed 68 ± 4% multi-lumen cysts in Wnt5a-KD versus 12 ± 3% in controls (Fig. 4B, p < 0.001)"',
56+
sourceSection: "Results",
57+
},
58+
{
59+
nodeType: "source",
60+
content: "Yamamoto et al. (2015) Nature Cell Biology 17(8):1024-1035",
61+
supportSnippet:
62+
"Primary research article on Wnt5a basolateral secretion and lumen formation in polarized epithelia.",
63+
sourceSection: "References",
64+
},
65+
{
66+
nodeType: "theory",
67+
content:
68+
"Non-canonical Wnt signaling through the planar cell polarity pathway is a conserved mechanism for epithelial lumen morphogenesis.",
69+
supportSnippet:
70+
'"Our findings place Wnt5a upstream of the PCP pathway in the regulation of epithelial lumen morphogenesis, consistent with the broader role of non-canonical Wnt signaling in tissue polarity"',
71+
sourceSection: "Discussion",
72+
},
73+
{
74+
nodeType: "evidence",
75+
content:
76+
"Co-immunoprecipitation showed that Wnt5a preferentially binds Ror2 receptor at the basolateral surface.",
77+
supportSnippet:
78+
'"IP-Western analysis demonstrated direct Wnt5a-Ror2 interaction in basolateral but not apical membrane fractions (Fig. 5C)"',
79+
sourceSection: "Results",
80+
},
81+
{
82+
nodeType: "claim",
83+
content:
84+
"Loss of Wnt5a function disrupts lumen formation in 3D cyst cultures derived from epithelial cells.",
85+
supportSnippet:
86+
'"These data demonstrate that Wnt5a is required for proper lumen formation in three-dimensional culture systems"',
87+
sourceSection: "Discussion",
88+
},
89+
];
90+
91+
const EXPANDED_INDICES = new Set([0, 1]);
92+
93+
const typeCounts = SAMPLE_NODES.reduce<Record<string, number>>((acc, node) => {
94+
acc[node.nodeType] = (acc[node.nodeType] ?? 0) + 1;
95+
return acc;
96+
}, {});
97+
98+
const TABS = [
99+
{ id: "all", label: "All", count: SAMPLE_NODES.length, color: undefined },
100+
...Object.entries(typeCounts).map(([nodeType, count]) => ({
101+
id: nodeType,
102+
label: nodeType.charAt(0).toUpperCase() + nodeType.slice(1),
103+
count,
104+
color: NODE_TYPE_COLORS[nodeType],
105+
})),
106+
];
107+
108+
export const MainContent = (): React.ReactElement => {
109+
return (
110+
<section className="flex min-h-[420px] flex-1 overflow-hidden rounded-[24px] border border-slate-200/85 bg-white shadow-[0_24px_48px_-36px_rgba(15,23,42,0.55)]">
111+
<div className="flex flex-1 flex-col overflow-hidden bg-[linear-gradient(180deg,#ffffff_0%,#f8fafc_100%)]">
112+
<div className="relative shrink-0 border-b border-slate-200/80 bg-[linear-gradient(135deg,#ffffff_0%,#f8fbff_100%)] px-4 py-4 lg:px-5">
113+
<div className="absolute inset-x-0 top-0 h-[2px] bg-[linear-gradient(90deg,#0ea5e9_0%,#22d3ee_45%,#34d399_100%)]" />
114+
<h2 className="text-[24px] font-semibold tracking-[-0.024em] text-slate-900">
115+
Basolateral secretion of Wnt5a in polarized epithelial cells is
116+
required for apical lumen formation
117+
</h2>
118+
<p className="mt-1 text-[15px] text-slate-500">
119+
Yamamoto H, Komekado H, Kikuchi A
120+
</p>
121+
</div>
122+
123+
<div className="shrink-0 border-b border-slate-200/70 bg-white/95 px-4 lg:px-5">
124+
<div className="flex gap-1 py-2">
125+
{TABS.map((tab) => (
126+
<Badge
127+
key={tab.id}
128+
variant={tab.id === "all" ? "default" : "outline"}
129+
className={
130+
tab.id === "all"
131+
? "bg-slate-900 px-3 py-1.5 text-[14px] font-semibold text-white hover:bg-slate-800"
132+
: "px-3 py-1.5 text-[14px] font-semibold text-slate-600"
133+
}
134+
>
135+
{tab.color && (
136+
<span
137+
className="mr-1.5 inline-block h-2 w-2 rounded-full"
138+
style={{ backgroundColor: tab.color }}
139+
/>
140+
)}
141+
{tab.label} {tab.count}
142+
</Badge>
143+
))}
144+
</div>
145+
</div>
146+
147+
<div className="flex-1 overflow-y-auto bg-[radial-gradient(120%_100%_at_50%_0%,#f8fbff_0%,#f8fafc_52%,#f3f7fb_100%)] p-4 lg:p-5">
148+
<div className="space-y-2.5">
149+
{SAMPLE_NODES.map((node, index) => {
150+
const color = NODE_TYPE_COLORS[node.nodeType] ?? "#64748b";
151+
const isExpanded = EXPANDED_INDICES.has(index);
152+
return (
153+
<Card key={index} className="rounded-2xl border-slate-200/85">
154+
<CardContent className="p-4">
155+
<div className="flex items-start gap-3">
156+
<Checkbox checked className="mt-1" />
157+
<div className="min-w-0 flex-1">
158+
<div className="mb-2 flex flex-wrap items-center gap-2">
159+
<span
160+
className="rounded px-1.5 py-0.5 text-[11px] font-bold uppercase tracking-wider text-white"
161+
style={{ backgroundColor: color }}
162+
>
163+
{node.nodeType}
164+
</span>
165+
{node.sourceSection && (
166+
<span className="text-[13px] text-slate-400">
167+
{node.sourceSection}
168+
</span>
169+
)}
170+
</div>
171+
<p className="text-[15px] leading-relaxed text-slate-800">
172+
{node.content}
173+
</p>
174+
{isExpanded ? (
175+
<>
176+
<div className="mt-2 rounded-lg bg-slate-50 px-3 py-2">
177+
<p className="text-[13px] italic leading-relaxed text-slate-500">
178+
{node.supportSnippet}
179+
</p>
180+
</div>
181+
<Button
182+
variant="ghost"
183+
size="sm"
184+
className="mt-1 h-auto p-0 text-[13px] font-medium text-slate-400 hover:text-slate-600"
185+
>
186+
Hide details
187+
</Button>
188+
</>
189+
) : (
190+
<Button
191+
variant="ghost"
192+
size="sm"
193+
className="mt-1 h-auto p-0 text-[13px] font-medium text-slate-400 hover:text-slate-600"
194+
>
195+
Show details
196+
</Button>
197+
)}
198+
</div>
199+
</div>
200+
</CardContent>
201+
</Card>
202+
);
203+
})}
204+
</div>
205+
</div>
206+
207+
<div className="flex shrink-0 flex-col gap-3 border-t border-slate-200/85 bg-white/95 px-4 py-3.5 backdrop-blur sm:flex-row sm:items-center sm:justify-between lg:px-5">
208+
<div className="flex items-center gap-2.5">
209+
<Button
210+
variant="outline"
211+
size="sm"
212+
className="rounded-full border-slate-200 text-slate-600"
213+
>
214+
Deselect All
215+
</Button>
216+
<span className="text-[14px] font-medium tabular-nums text-slate-500">
217+
{SAMPLE_NODES.length} of {SAMPLE_NODES.length} selected
218+
</span>
219+
</div>
220+
221+
<Button className="gap-2 rounded-full bg-slate-900 text-white hover:bg-slate-800">
222+
<Copy className="h-4 w-4" />
223+
Copy to Clipboard
224+
</Button>
225+
</div>
226+
</div>
227+
</section>
228+
);
229+
};
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { Button } from "@repo/ui/components/ui/button";
2+
import { Checkbox } from "@repo/ui/components/ui/checkbox";
3+
import { ChevronDown } from "lucide-react";
4+
5+
const NODE_TYPES = [
6+
{
7+
label: "Claim",
8+
description:
9+
"An assertion about how something works or should work. Usually a single declarative sentence.",
10+
candidateTag: "#clm-candidate",
11+
color: "#7DA13E",
12+
},
13+
{
14+
label: "Question",
15+
description:
16+
"An unknown being explored through research. Framed as a question that can be investigated.",
17+
candidateTag: "#que-candidate",
18+
color: "#99890E",
19+
},
20+
{
21+
label: "Hypothesis",
22+
description:
23+
"A proposed answer to a question. Collects evidence for or against.",
24+
candidateTag: "#hyp-candidate",
25+
color: "#7C4DFF",
26+
},
27+
{
28+
label: "Evidence",
29+
description:
30+
"A discrete observation from a published dataset or experiment. Usually in past tense with observable, model system, and method.",
31+
candidateTag: "#evd-candidate",
32+
color: "#dc0c4a",
33+
},
34+
{
35+
label: "Result",
36+
description:
37+
"A discrete observation from ongoing research, in past tense. Includes source context.",
38+
candidateTag: "#res-candidate",
39+
color: "#E6A23C",
40+
},
41+
{
42+
label: "Source",
43+
description: "A referenced publication or external resource.",
44+
candidateTag: "#src-candidate",
45+
color: "#9E9E9E",
46+
},
47+
{
48+
label: "Theory",
49+
description: "A theoretical framework or model that explains phenomena.",
50+
candidateTag: "#the-candidate",
51+
color: "#8B5CF6",
52+
},
53+
];
54+
55+
const SECTION_LABEL_CLASS =
56+
"mb-3 block px-1 text-[18px] font-semibold tracking-[-0.016em] text-slate-800";
57+
58+
export const Sidebar = (): React.ReactElement => {
59+
return (
60+
<aside className="flex w-full shrink-0 flex-col overflow-hidden rounded-[24px] border border-slate-200/85 bg-white shadow-[0_26px_52px_-38px_rgba(15,23,42,0.6)] lg:w-[390px] xl:w-[420px]">
61+
<div className="flex-1 overflow-y-auto p-4 lg:p-5">
62+
<section className="mb-6">
63+
<h3 className={SECTION_LABEL_CLASS}>Paper</h3>
64+
<div className="flex w-full items-start gap-3 rounded-2xl border border-slate-200 bg-white p-3.5">
65+
<div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-gradient-to-b from-rose-500 to-rose-600">
66+
<span className="text-[11px] font-bold tracking-[0.02em] text-white">
67+
PDF
68+
</span>
69+
</div>
70+
<div className="min-w-0 flex-1 pt-0.5">
71+
<p className="truncate text-[16px] font-semibold leading-tight text-slate-900">
72+
Yamamoto et al. - 2015 - Basolate...
73+
</p>
74+
<p className="mt-1 text-[14px] leading-tight text-slate-500">
75+
7.8 MB &middot;{" "}
76+
<span className="font-medium text-slate-500">Replace file</span>
77+
</p>
78+
</div>
79+
</div>
80+
</section>
81+
82+
<section className="mb-6">
83+
<h3 className={SECTION_LABEL_CLASS}>Model</h3>
84+
<Button
85+
variant="outline"
86+
className="w-full justify-between rounded-xl border-slate-300 px-3.5 py-3 text-[16px] font-medium text-slate-700"
87+
>
88+
<span>Claude Sonnet 4.6</span>
89+
<ChevronDown className="h-4 w-4 text-slate-500" />
90+
</Button>
91+
</section>
92+
93+
<section className="mb-5">
94+
<h3 className={SECTION_LABEL_CLASS}>Research Question</h3>
95+
<div className="w-full rounded-xl border border-slate-300 bg-white px-3.5 py-3 text-[16px] text-slate-700">
96+
What are the molecular determinants of lumenoid formation in hiPSCs?
97+
</div>
98+
</section>
99+
100+
<div className="mx-1 mb-5 border-t border-slate-200" />
101+
102+
<section>
103+
<div className="mb-2.5 flex items-center justify-between px-1">
104+
<h3 className="text-[18px] font-semibold tracking-[-0.016em] text-slate-800">
105+
Node Types
106+
</h3>
107+
<span className="text-[13px] font-semibold tabular-nums text-slate-500">
108+
{NODE_TYPES.length}/{NODE_TYPES.length}
109+
</span>
110+
</div>
111+
112+
<div className="space-y-1.5">
113+
{NODE_TYPES.map((type) => (
114+
<label
115+
key={type.candidateTag}
116+
className="flex w-full cursor-pointer items-center gap-2.5 rounded-xl border border-slate-200 bg-white px-2.5 py-2.5 text-slate-800 shadow-sm"
117+
>
118+
<Checkbox checked />
119+
<span className="min-w-0 flex-1 text-[16px] font-medium">
120+
{type.label}
121+
</span>
122+
<span className="shrink-0 text-[11px] font-medium text-slate-400">
123+
{type.candidateTag}
124+
</span>
125+
</label>
126+
))}
127+
</div>
128+
</section>
129+
</div>
130+
131+
<div className="border-t border-slate-200/90 bg-white/95 p-4 backdrop-blur-xl">
132+
<p className="mb-2 text-[14px] font-medium text-slate-500">
133+
Ready to run extraction.
134+
</p>
135+
<Button className="w-full rounded-xl bg-slate-900 py-6 text-[17px] font-semibold text-white hover:bg-slate-800">
136+
Re-Extract
137+
</Button>
138+
</div>
139+
</aside>
140+
);
141+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { MainContent } from "./components/MainContent";
2+
import { Sidebar } from "./components/Sidebar";
3+
4+
const ExtractNodesPage = (): React.ReactElement => {
5+
return (
6+
<div className="flex h-full w-full flex-1 flex-col gap-4 p-4 lg:flex-row lg:gap-5 lg:p-5">
7+
<Sidebar />
8+
<MainContent />
9+
</div>
10+
);
11+
};
12+
13+
export default ExtractNodesPage;

0 commit comments

Comments
 (0)