reversely commited on
Commit
6f2b0ba
·
verified ·
1 Parent(s): b0c3013

Upload folder using huggingface_hub

Browse files
frontend/public/case-study/BlakeGilks.jpg ADDED
frontend/public/case-study/assets/study.css CHANGED
@@ -135,6 +135,7 @@ blockquote.pull p,
135
  .cap-list,
136
  .ref-list,
137
  .ledge span,
 
138
  .study-table caption,
139
  .study-table th,
140
  .study-table td {
@@ -684,32 +685,41 @@ blockquote.pull p {
684
  color: var(--ink-meta);
685
  }
686
 
687
- /* A specimen tile: the transparent tissue figure floats on its own light card, so it reads as preserved
688
- material lifted out of the dark archive. The card carries its own dark ink regardless of the biome. */
689
- .tissue-figure {
690
- max-width: 560px;
691
  margin: 6vh auto 0;
692
- padding: 24px 26px 20px;
693
- background: #efe9dd;
694
- border: 1px solid rgba(28, 23, 16, 0.14);
695
- border-radius: 10px;
696
- box-shadow: 0 16px 46px rgba(0, 0, 0, 0.42);
697
- text-align: center;
698
  }
699
  .tissue-figure img {
700
  display: block;
701
  width: 100%;
702
- max-width: 300px;
703
  height: auto;
704
- margin: 0 auto;
705
  }
706
  .tissue-figure figcaption {
707
- margin: 16px auto 0;
708
- max-width: 44ch;
709
  font-style: italic;
710
- font-size: 12px;
711
- line-height: 1.55;
712
- color: rgba(28, 23, 16, 0.62);
 
 
 
 
 
 
 
 
 
 
 
 
713
  }
714
 
715
  /* ------------------------------ In-prose lists ------------------------------ */
 
135
  .cap-list,
136
  .ref-list,
137
  .ledge span,
138
+ .ledge strong,
139
  .study-table caption,
140
  .study-table th,
141
  .study-table td {
 
685
  color: var(--ink-meta);
686
  }
687
 
688
+ /* Section 03 carries the light background itself (the paper biome), so the transparent specimen figure needs
689
+ no card. It floats into the column and the closing prose wraps around it. */
690
+ .stats-tissue {
691
+ max-width: 720px;
692
  margin: 6vh auto 0;
693
+ overflow: hidden; /* contain the float so the wrapper spans the figure */
694
+ }
695
+ .tissue-figure {
696
+ float: right;
697
+ width: 248px;
698
+ margin: 4px 0 16px 34px;
699
  }
700
  .tissue-figure img {
701
  display: block;
702
  width: 100%;
 
703
  height: auto;
 
704
  }
705
  .tissue-figure figcaption {
706
+ margin-top: 12px;
 
707
  font-style: italic;
708
+ font-size: 11.5px;
709
+ line-height: 1.5;
710
+ color: var(--ink-meta);
711
+ }
712
+ .stats-tissue .stats-foot {
713
+ max-width: none;
714
+ margin: 0;
715
+ }
716
+ @media (max-width: 640px) {
717
+ .tissue-figure {
718
+ float: none;
719
+ width: 100%;
720
+ max-width: 300px;
721
+ margin: 0 auto 22px;
722
+ }
723
  }
724
 
725
  /* ------------------------------ In-prose lists ------------------------------ */
frontend/public/case-study/index.html CHANGED
@@ -332,7 +332,7 @@
332
  </div>
333
 
334
  <!-- ============ 3. Section 03: when the definition changes (numbers on terraces) ============ -->
335
- <section class="stats" data-biome="records">
336
  <div class="stats-head">
337
  <p class="eyebrow">03.</p>
338
  <h2 class="stats-title">When the definition of the disease changes</h2>
@@ -356,19 +356,25 @@
356
  <li class="ledge"><strong>42%</strong><span>of patients had mismatch-repair testing at diagnosis, coverage ranging 3.5% to 95.4% across centres (Thompson et&nbsp;al., 2022)</span></li>
357
  <li class="ledge"><strong>3</strong><span>layers to the reconstruction problem</span></li>
358
  </ol>
359
- <p class="stats-foot">
360
- A case recorded as stage IA under FIGO 2009 may need histologic review, lymphovascular space invasion
361
- assessment, p53 immunohistochemistry, mismatch-repair testing, or POLE sequencing before a current
362
- stage can be assigned. The historical cohort still helps: archives often preserve tissue blocks that
363
- support later testing, so higher-resolution evidence can be generated years after diagnosis.
364
- </p>
365
- <figure class="tissue-figure reveal">
366
- <img src="./assets/papers/hjelmeland.png" alt="Vimentin immunohistochemistry of endometrial tumour tissue at three staining intensities" loading="lazy" />
367
- <figcaption>
368
- Vimentin immunohistochemistry on archived endometrial tumour tissue, graded by staining intensity.
369
- Hjelmeland et al., BJC Reports 2, 81 (2024).
370
- </figcaption>
371
- </figure>
 
 
 
 
 
 
372
  </section>
373
 
374
  <!-- ==== 4. The three layers (descend-then-sideways rig, workflow biome) ==== -->
 
332
  </div>
333
 
334
  <!-- ============ 3. Section 03: when the definition changes (numbers on terraces) ============ -->
335
+ <section class="stats" data-biome="paper">
336
  <div class="stats-head">
337
  <p class="eyebrow">03.</p>
338
  <h2 class="stats-title">When the definition of the disease changes</h2>
 
356
  <li class="ledge"><strong>42%</strong><span>of patients had mismatch-repair testing at diagnosis, coverage ranging 3.5% to 95.4% across centres (Thompson et&nbsp;al., 2022)</span></li>
357
  <li class="ledge"><strong>3</strong><span>layers to the reconstruction problem</span></li>
358
  </ol>
359
+ <div class="stats-tissue">
360
+ <figure class="tissue-figure reveal">
361
+ <img src="./assets/papers/hjelmeland.png" alt="Vimentin immunohistochemistry of endometrial tumour tissue at three staining intensities" loading="lazy" />
362
+ <figcaption>
363
+ Vimentin immunohistochemistry on archived endometrial tumour tissue, graded by staining intensity.
364
+ Hjelmeland et al., BJC Reports 2, 81 (2024).
365
+ </figcaption>
366
+ </figure>
367
+ <p class="stats-foot">
368
+ A case recorded as stage IA under FIGO 2009 may need histologic review, lymphovascular space invasion
369
+ assessment, p53 immunohistochemistry, mismatch-repair testing, or POLE sequencing before a current
370
+ stage can be assigned. The historical cohort still helps: archives often preserve tissue blocks that
371
+ support later testing, so higher-resolution evidence can be generated years after diagnosis. The
372
+ practical work can be intensive: a team may need to retrieve reports, locate the corresponding tissue,
373
+ order new assays, identify which standard applies to each historical interpretation, join the new
374
+ results to the correct patient and specimen, and preserve why the final study value differs from the
375
+ original clinical label.
376
+ </p>
377
+ </div>
378
  </section>
379
 
380
  <!-- ==== 4. The three layers (descend-then-sideways rig, workflow biome) ==== -->
frontend/src/index.css CHANGED
@@ -3199,6 +3199,8 @@ h2 {
3199
  .mapping .mp-chat-form {
3200
  display: flex;
3201
  gap: 8px;
 
 
3202
  }
3203
  .mapping .mp-chat-form .mp-input {
3204
  flex: 1;
@@ -3207,6 +3209,18 @@ h2 {
3207
  color: #1c2b36;
3208
  border: 1px solid var(--block-hairline);
3209
  }
 
 
 
 
 
 
 
 
 
 
 
 
3210
  .mapping .mp-chat-form .mp-input::placeholder {
3211
  color: #8a97a3;
3212
  }
@@ -3963,10 +3977,23 @@ h2 {
3963
  .cr .cr-chat-form {
3964
  display: flex;
3965
  gap: 8px;
 
 
3966
  }
3967
  .cr .cr-chat-form .cr-input {
3968
  flex: 1;
3969
  }
 
 
 
 
 
 
 
 
 
 
 
3970
 
3971
  /* --- Decision ledger --- */
3972
  .cr .cr-ledger {
 
3199
  .mapping .mp-chat-form {
3200
  display: flex;
3201
  gap: 8px;
3202
+ /* The input grows with its content; keep the Ask button pinned to the bottom edge as it does. */
3203
+ align-items: flex-end;
3204
  }
3205
  .mapping .mp-chat-form .mp-input {
3206
  flex: 1;
 
3209
  color: #1c2b36;
3210
  border: 1px solid var(--block-hairline);
3211
  }
3212
+ /* An auto-growing textarea: one line to start, grows with the message (see the useLayoutEffect that sets
3213
+ its height from scrollHeight), then scrolls past a cap so it never runs off the panel. */
3214
+ .mapping .mp-chat-form textarea.mp-input {
3215
+ height: auto;
3216
+ min-height: 36px;
3217
+ max-height: 132px;
3218
+ padding: 8px 10px;
3219
+ line-height: 1.4;
3220
+ resize: none;
3221
+ overflow-y: auto;
3222
+ box-sizing: border-box;
3223
+ }
3224
  .mapping .mp-chat-form .mp-input::placeholder {
3225
  color: #8a97a3;
3226
  }
 
3977
  .cr .cr-chat-form {
3978
  display: flex;
3979
  gap: 8px;
3980
+ /* Keep the Ask button at the bottom edge as the input grows with its content. */
3981
+ align-items: flex-end;
3982
  }
3983
  .cr .cr-chat-form .cr-input {
3984
  flex: 1;
3985
  }
3986
+ /* An auto-growing textarea (see the useLayoutEffect setting its height from scrollHeight): one line to
3987
+ start, grows with the message, then scrolls past a cap. */
3988
+ .cr .cr-chat-form textarea.cr-input {
3989
+ height: auto;
3990
+ min-height: 32px;
3991
+ max-height: 120px;
3992
+ line-height: 1.4;
3993
+ resize: none;
3994
+ overflow-y: auto;
3995
+ box-sizing: border-box;
3996
+ }
3997
 
3998
  /* --- Decision ledger --- */
3999
  .cr .cr-ledger {
frontend/src/pages/CaseReview.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
  import { Link, useNavigate, useParams, useSearchParams } from "react-router-dom";
3
  import {
4
  askFieldChat,
@@ -1056,14 +1056,33 @@ function ValueChat({
1056
  onFileChat: (r: FieldChatResponse, q: string) => void;
1057
  }) {
1058
  const [draft, setDraft] = useState("");
 
1059
 
1060
- function submit(e: React.FormEvent) {
1061
- e.preventDefault();
 
 
 
 
 
 
 
1062
  const q = draft.trim();
1063
  if (!q || busy) return;
1064
  onAsk(q);
1065
  setDraft("");
1066
  }
 
 
 
 
 
 
 
 
 
 
 
1067
 
1068
  return (
1069
  <div className="cr-chat">
@@ -1115,12 +1134,15 @@ function ValueChat({
1115
  {busy && <p className="cr-chat-hint">Asking the model…</p>}
1116
  </div>
1117
  <form className="cr-chat-form" onSubmit={submit}>
1118
- <input
 
1119
  className="cr-input"
 
1120
  value={draft}
1121
  placeholder={`e.g. why is ${block.label} ${formatValue(block.value) || "blank"}?`}
1122
  aria-label="Ask about this value"
1123
  onChange={(e) => setDraft(e.target.value)}
 
1124
  />
1125
  <button type="submit" className="cr-btn-secondary" disabled={busy || !draft.trim()}>
1126
  Ask
 
1
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
2
  import { Link, useNavigate, useParams, useSearchParams } from "react-router-dom";
3
  import {
4
  askFieldChat,
 
1056
  onFileChat: (r: FieldChatResponse, q: string) => void;
1057
  }) {
1058
  const [draft, setDraft] = useState("");
1059
+ const inputRef = useRef<HTMLTextAreaElement>(null);
1060
 
1061
+ // Grow the input with its content as a long question is typed, up to a cap where the field scrolls.
1062
+ useLayoutEffect(() => {
1063
+ const el = inputRef.current;
1064
+ if (!el) return;
1065
+ el.style.height = "auto";
1066
+ el.style.height = `${el.scrollHeight}px`;
1067
+ }, [draft]);
1068
+
1069
+ function doSubmit() {
1070
  const q = draft.trim();
1071
  if (!q || busy) return;
1072
  onAsk(q);
1073
  setDraft("");
1074
  }
1075
+ function submit(e: React.FormEvent) {
1076
+ e.preventDefault();
1077
+ doSubmit();
1078
+ }
1079
+ function onInputKey(e: React.KeyboardEvent) {
1080
+ // Enter sends; Shift+Enter inserts a newline.
1081
+ if (e.key === "Enter" && !e.shiftKey) {
1082
+ e.preventDefault();
1083
+ doSubmit();
1084
+ }
1085
+ }
1086
 
1087
  return (
1088
  <div className="cr-chat">
 
1134
  {busy && <p className="cr-chat-hint">Asking the model…</p>}
1135
  </div>
1136
  <form className="cr-chat-form" onSubmit={submit}>
1137
+ <textarea
1138
+ ref={inputRef}
1139
  className="cr-input"
1140
+ rows={1}
1141
  value={draft}
1142
  placeholder={`e.g. why is ${block.label} ${formatValue(block.value) || "blank"}?`}
1143
  aria-label="Ask about this value"
1144
  onChange={(e) => setDraft(e.target.value)}
1145
+ onKeyDown={onInputKey}
1146
  />
1147
  <button type="submit" className="cr-btn-secondary" disabled={busy || !draft.trim()}>
1148
  Ask
frontend/src/pages/Mapping.tsx CHANGED
@@ -1097,14 +1097,34 @@ function EdgeChat({
1097
  }) {
1098
  const [draft, setDraft] = useState("");
1099
  const identityReady = identity.name.trim() && identity.role.trim();
 
1100
 
1101
- function submit(event: React.FormEvent) {
1102
- event.preventDefault();
 
 
 
 
 
 
 
 
1103
  const q = draft.trim();
1104
  if (!q || busy) return;
1105
  askEdge(edge, q);
1106
  setDraft("");
1107
  }
 
 
 
 
 
 
 
 
 
 
 
1108
 
1109
  function applyRemap(entry: Extract<ChatEntry, { kind: "assistant" }>) {
1110
  const p = entry.response.proposal;
@@ -1191,12 +1211,15 @@ function EdgeChat({
1191
  </p>
1192
  )}
1193
  <form className="mp-chat-form" onSubmit={submit}>
1194
- <input
 
1195
  className="mp-input"
 
1196
  value={draft}
1197
- placeholder="e.g. why margins here?"
1198
  aria-label="Ask about this edge"
1199
  onChange={(e) => setDraft(e.target.value)}
 
1200
  />
1201
  <button type="submit" className="mp-btn-secondary" disabled={busy || !draft.trim()}>
1202
  Ask
 
1097
  }) {
1098
  const [draft, setDraft] = useState("");
1099
  const identityReady = identity.name.trim() && identity.role.trim();
1100
+ const inputRef = useRef<HTMLTextAreaElement>(null);
1101
 
1102
+ // Grow the input with its content, so a long question is fully visible as it is typed, up to a cap
1103
+ // where the field itself scrolls. Runs on every draft change, including the reset to "" after a send.
1104
+ useLayoutEffect(() => {
1105
+ const el = inputRef.current;
1106
+ if (!el) return;
1107
+ el.style.height = "auto";
1108
+ el.style.height = `${el.scrollHeight}px`;
1109
+ }, [draft]);
1110
+
1111
+ function doSubmit() {
1112
  const q = draft.trim();
1113
  if (!q || busy) return;
1114
  askEdge(edge, q);
1115
  setDraft("");
1116
  }
1117
+ function submit(event: React.FormEvent) {
1118
+ event.preventDefault();
1119
+ doSubmit();
1120
+ }
1121
+ function onInputKey(event: React.KeyboardEvent) {
1122
+ // Enter sends; Shift+Enter inserts a newline, the usual chat convention.
1123
+ if (event.key === "Enter" && !event.shiftKey) {
1124
+ event.preventDefault();
1125
+ doSubmit();
1126
+ }
1127
+ }
1128
 
1129
  function applyRemap(entry: Extract<ChatEntry, { kind: "assistant" }>) {
1130
  const p = entry.response.proposal;
 
1211
  </p>
1212
  )}
1213
  <form className="mp-chat-form" onSubmit={submit}>
1214
+ <textarea
1215
+ ref={inputRef}
1216
  className="mp-input"
1217
+ rows={1}
1218
  value={draft}
1219
+ placeholder="e.g. why this concept?"
1220
  aria-label="Ask about this edge"
1221
  onChange={(e) => setDraft(e.target.value)}
1222
+ onKeyDown={onInputKey}
1223
  />
1224
  <button type="submit" className="mp-btn-secondary" disabled={busy || !draft.trim()}>
1225
  Ask