@tiagojct/subsub 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/web/app.js CHANGED
@@ -67,6 +67,24 @@ marked.use({
67
67
  image(token) {
68
68
  return escapeHtml(token.text || token.href || "");
69
69
  },
70
+ table(token) {
71
+ let headerHtml = "";
72
+ for (let r = 0; r < token.header.length; r++) {
73
+ headerHtml += this.tablecell(token.header[r]);
74
+ }
75
+ let t = this.tablerow({ text: headerHtml });
76
+ let s = "";
77
+ for (let r = 0; r < token.rows.length; r++) {
78
+ let row = token.rows[r];
79
+ let n = "";
80
+ for (let o = 0; o < row.length; o++) {
81
+ n += this.tablecell(row[o]);
82
+ }
83
+ s += this.tablerow({ text: n });
84
+ }
85
+ if (s) s = `<tbody>${s}</tbody>`;
86
+ return `<div class="md-table-wrap"><table><thead>${t}</thead>${s}</table></div>`;
87
+ },
70
88
  },
71
89
  });
72
90
  function md(text) {
@@ -77,9 +95,25 @@ function md(text) {
77
95
  }
78
96
  }
79
97
 
80
- // ---------------------------------------------------------------- theme & language
98
+ // ---------------------------------------------------------------- theme, layout & language
81
99
 
82
100
  let currentTheme = localStorage.getItem("subsub-theme") || "auto";
101
+ let currentLayout = localStorage.getItem("subsub-layout") || "standard";
102
+
103
+ function updateLayoutUI() {
104
+ const isWide = currentLayout === "wide";
105
+ document.body.classList.toggle("layout-full", isWide);
106
+ const textEl = $("width-btn-text");
107
+ if (textEl) textEl.textContent = isWide ? t("layoutFocus") : t("layoutWide");
108
+ const btn = $("width-btn");
109
+ if (btn) btn.title = t("toggleWidth");
110
+ }
111
+
112
+ function toggleLayout() {
113
+ currentLayout = currentLayout === "wide" ? "standard" : "wide";
114
+ localStorage.setItem("subsub-layout", currentLayout);
115
+ updateLayoutUI();
116
+ }
83
117
 
84
118
  function themeLabel(theme) {
85
119
  if (theme === "light") return t("themeLight");
@@ -130,6 +164,8 @@ function renderHeader() {
130
164
  document.body.dataset.mode = mode();
131
165
  for (const b of document.querySelectorAll(".modes button")) b.setAttribute("aria-pressed", String(b.dataset.mode === mode()));
132
166
  if (state?.profile) $("profile").value = $("profile-side").value = state.profile;
167
+ $("starbuck-toggle").checked = Boolean(state?.starbuck);
168
+ $("starbuck-toggle").parentElement.title = t("starbuckHint");
133
169
  const m = state?.model ?? session?.model;
134
170
  $("model-btn").textContent = $("model-btn-side").textContent = m ? m.id : t("noModel");
135
171
  const lib = $("library");
@@ -166,7 +202,9 @@ function hasCommand(name) {
166
202
  function renderActions() {
167
203
  const box = $("actions");
168
204
  box.replaceChildren();
169
- const list = [...ACTIONS[mode()]].filter((a) => hasCommand(a.cmd));
205
+ const list = [...ACTIONS[mode()]].filter(
206
+ (a) => hasCommand(a.cmd) && (!a.needs || state?.[a.needs]) && (!a.profiles || a.profiles.includes(state?.profile ?? "scholar")),
207
+ );
170
208
  for (const a of list) box.append(actionButton(a));
171
209
  box.append(el("hr"));
172
210
  for (const a of ACTIONS.both.filter((x) => hasCommand(x.cmd))) box.append(actionButton(a));
@@ -194,7 +232,7 @@ function renderBanner() {
194
232
  b.replaceChildren();
195
233
  const m = state?.model ?? session?.model;
196
234
  if (!m && session) {
197
- b.append(el("span", { text: t("connectModel") }), el("button", { type: "button", class: "primary small", text: t("connect"), onclick: openModels }));
235
+ b.append(el("span", { text: t("connectModel") }), el("button", { type: "button", class: "primary small", text: t("connect"), onclick: showModels }));
198
236
  b.hidden = false;
199
237
  } else b.hidden = true;
200
238
  }
@@ -790,6 +828,11 @@ function nextDialog() {
790
828
 
791
829
  // ---------------------------------------------------------------- models and keys
792
830
 
831
+ /** Open the model dialog; a failure becomes a note, not an unhandled rejection. */
832
+ function showModels() {
833
+ openModels().catch((err) => note(err.message, "warning"));
834
+ }
835
+
793
836
  async function openModels() {
794
837
  const dlg = $("model-dialog");
795
838
  const ul = $("models");
@@ -973,11 +1016,24 @@ for (const b of document.querySelectorAll(".modes button")) {
973
1016
  });
974
1017
  }
975
1018
  $("profile").addEventListener("change", (e) => sendMessage(`/profile ${e.target.value}`));
1019
+ $("starbuck-toggle").addEventListener("change", async (e) => {
1020
+ const on = e.target.checked;
1021
+ e.target.disabled = true;
1022
+ try {
1023
+ await api("/api/addons", { starbuck: on });
1024
+ note(t(on ? "starbuckOn" : "starbuckOff"), "info");
1025
+ } catch (err) {
1026
+ e.target.checked = !on;
1027
+ note(err.message, "warning");
1028
+ } finally {
1029
+ e.target.disabled = false;
1030
+ }
1031
+ });
976
1032
  $("profile-side").addEventListener("change", (e) => sendMessage(`/profile ${e.target.value}`));
977
- $("model-btn").addEventListener("click", openModels);
1033
+ $("model-btn").addEventListener("click", showModels);
978
1034
  $("model-btn-side").addEventListener("click", () => {
979
1035
  closeSide();
980
- openModels();
1036
+ showModels();
981
1037
  });
982
1038
  $("model-close").addEventListener("click", () => $("model-dialog").close());
983
1039
  $("main").addEventListener("click", () => {
@@ -985,10 +1041,19 @@ $("main").addEventListener("click", () => {
985
1041
  });
986
1042
  $("menu-btn").addEventListener("click", (e) => {
987
1043
  e.stopPropagation();
988
- const side = $("side");
989
- side.classList.toggle("open");
990
- $("menu-btn").setAttribute("aria-expanded", String(side.classList.contains("open")));
1044
+ if (window.innerWidth > 832) {
1045
+ const app = document.querySelector(".app");
1046
+ app.classList.toggle("side-collapsed");
1047
+ const collapsed = app.classList.contains("side-collapsed");
1048
+ $("menu-btn").setAttribute("aria-expanded", String(!collapsed));
1049
+ localStorage.setItem("subsub-side-collapsed", String(collapsed));
1050
+ } else {
1051
+ const side = $("side");
1052
+ side.classList.toggle("open");
1053
+ $("menu-btn").setAttribute("aria-expanded", String(side.classList.contains("open")));
1054
+ }
991
1055
  });
1056
+ $("width-btn")?.addEventListener("click", toggleLayout);
992
1057
  $("theme-btn")?.addEventListener("click", toggleTheme);
993
1058
  $("theme-btn-side")?.addEventListener("click", () => {
994
1059
  closeSide();
@@ -1029,5 +1094,10 @@ function connect() {
1029
1094
  }
1030
1095
 
1031
1096
  setTheme(currentTheme);
1097
+ updateLayoutUI();
1098
+ if (localStorage.getItem("subsub-side-collapsed") === "true" && window.innerWidth > 832) {
1099
+ document.querySelector(".app")?.classList.add("side-collapsed");
1100
+ $("menu-btn")?.setAttribute("aria-expanded", "false");
1101
+ }
1032
1102
  applyLang();
1033
1103
  connect();
package/web/glauca.css CHANGED
@@ -1,23 +1,23 @@
1
- /* Generated from glauca.json. Edit the json, then `make generate`. */
1
+ /* Sub-Sub Glauca tokens: Vintage & Minimal Bookmaking System */
2
2
  :root {
3
- --gl-font-serif: "IBM Plex Serif", "IBM Plex Serif fallback", Georgia, serif;
4
- --gl-font-sans: "IBM Plex Sans", "IBM Plex Sans fallback", system-ui, sans-serif;
5
- --gl-font-mono: "IBM Plex Mono", "IBM Plex Mono fallback", ui-monospace, monospace;
6
- --gl-font-reading: "IBM Plex Sans", "IBM Plex Sans fallback", system-ui, sans-serif;
7
- --gl-text-xs: 0.64rem;
8
- --gl-text-sm: 0.8rem;
9
- --gl-text-base: 1rem;
3
+ --gl-font-serif: "EB Garamond", "Newsreader", "IBM Plex Serif", Georgia, serif;
4
+ --gl-font-sans: "EB Garamond", "IBM Plex Sans", system-ui, -apple-system, sans-serif;
5
+ --gl-font-mono: "JetBrains Mono", "Courier Prime", "IBM Plex Mono", ui-monospace, monospace;
6
+ --gl-font-reading: "EB Garamond", Garamond, Georgia, serif;
7
+ --gl-text-xs: 0.72rem;
8
+ --gl-text-sm: 0.88rem;
9
+ --gl-text-base: 1.05rem;
10
10
  --gl-text-lg: 1.25rem;
11
- --gl-text-xl: 1.563rem;
12
- --gl-text-2xl: 1.953rem;
13
- --gl-text-3xl: 2.441rem;
14
- --gl-text-4xl: 3.052rem;
15
- --gl-text-display: clamp(2.5rem, 6vw, 4rem);
16
- --gl-text-poster: clamp(3rem, 10vw, 6.5rem);
17
- --gl-leading-tight: 1.08;
18
- --gl-leading-snug: 1.3;
19
- --gl-leading-normal: 1.55;
20
- --gl-leading-relaxed: 1.7;
11
+ --gl-text-xl: 1.55rem;
12
+ --gl-text-2xl: 1.95rem;
13
+ --gl-text-3xl: 2.45rem;
14
+ --gl-text-4xl: 3.05rem;
15
+ --gl-text-display: clamp(2.4rem, 5vw, 3.6rem);
16
+ --gl-text-poster: clamp(3rem, 8vw, 5.5rem);
17
+ --gl-leading-tight: 1.15;
18
+ --gl-leading-snug: 1.35;
19
+ --gl-leading-normal: 1.65;
20
+ --gl-leading-relaxed: 1.8;
21
21
  --gl-weight-regular: 400;
22
22
  --gl-weight-medium: 500;
23
23
  --gl-weight-semibold: 600;
@@ -43,64 +43,45 @@
43
43
  --gl-border-width: 1px;
44
44
  }
45
45
 
46
- /* Pruina is the light default; Profundum is dark. */
46
+ /* Light Default: Warm Archival Paper & Printer's Inks */
47
47
  :root,
48
48
  [data-mode="light"],
49
49
  [data-theme="light"],
50
50
  html[data-theme="light"] {
51
- --gl-bg: #f0f4f6;
52
- --gl-surface: #f7fafb;
51
+ --gl-bg: #f8f6f0;
52
+ --gl-surface: #f2ede2;
53
53
  --gl-surface-raised: #ffffff;
54
- --gl-text: #16222a;
55
- --gl-text-muted: #55646d;
56
- --gl-border: #cdd7dc;
57
- --gl-accent: #0b62cf;
58
- --gl-accent-bright: #007aff;
59
- --gl-accent-deep: #084b96;
60
- --gl-on-accent: #f2f7fb;
61
- --gl-tint-deep: #16303f;
62
- --gl-tint: #35576b;
63
- --gl-tint-bright: #3e6d84;
64
- --gl-tint-pale: #b7cdd6;
65
- --gl-on-tint: #f2f7fb;
54
+ --gl-text: #211e1b;
55
+ --gl-text-muted: #5c554a;
56
+ --gl-border: #dfd7c6;
57
+ --gl-accent: #234b6e;
58
+ --gl-accent-bright: #2f648e;
59
+ --gl-accent-deep: #15344e;
60
+ --gl-on-accent: #f8f6f0;
61
+ --gl-tint-deep: #182a36;
62
+ --gl-tint: #2c4659;
63
+ --gl-tint-bright: #486980;
64
+ --gl-tint-pale: #c4d3dc;
65
+ --gl-on-tint: #f8f6f0;
66
66
  }
67
67
 
68
- [data-mode="dark"],
68
+ /* Dark Mode: Antique Study by Candlelight */
69
69
  [data-theme="dark"],
70
- html[data-theme="dark"] {
71
- --gl-bg: #10161c;
72
- --gl-surface: #171f26;
73
- --gl-surface-raised: #1f2932;
74
- --gl-text: #e8eef2;
75
- --gl-text-muted: #8c8c8c;
76
- --gl-border: #2a3540;
77
- --gl-accent: #3d97ff;
78
- --gl-accent-bright: #6cb2ff;
79
- --gl-accent-deep: #007aff;
80
- --gl-on-accent: #0b1218;
81
- --gl-tint-deep: #142430;
82
- --gl-tint: #2b4356;
83
- --gl-tint-bright: #4d7391;
84
- --gl-tint-pale: #93b7c9;
85
- --gl-on-tint: #0d1720;
86
- }
87
-
88
- @media (prefers-color-scheme: dark) {
89
- :root:not([data-theme="light"]) {
90
- --gl-bg: #10161c;
91
- --gl-surface: #171f26;
92
- --gl-surface-raised: #1f2932;
93
- --gl-text: #e8eef2;
94
- --gl-text-muted: #8c8c8c;
95
- --gl-border: #2a3540;
96
- --gl-accent: #3d97ff;
97
- --gl-accent-bright: #6cb2ff;
98
- --gl-accent-deep: #007aff;
99
- --gl-on-accent: #0b1218;
100
- --gl-tint-deep: #142430;
101
- --gl-tint: #2b4356;
102
- --gl-tint-bright: #4d7391;
103
- --gl-tint-pale: #93b7c9;
104
- --gl-on-tint: #0d1720;
105
- }
70
+ html[data-theme="dark"],
71
+ [data-mode="dark"] {
72
+ --gl-bg: #141210;
73
+ --gl-surface: #1c1916;
74
+ --gl-surface-raised: #24201c;
75
+ --gl-text: #eae4d5;
76
+ --gl-text-muted: #9e9585;
77
+ --gl-border: #2e2924;
78
+ --gl-accent: #6ea3cc;
79
+ --gl-accent-bright: #8bb9dc;
80
+ --gl-accent-deep: #4a7e9f;
81
+ --gl-on-accent: #141210;
82
+ --gl-tint-deep: #0f181f;
83
+ --gl-tint: #243642;
84
+ --gl-tint-bright: #3d596b;
85
+ --gl-tint-pale: #7a9cb3;
86
+ --gl-on-tint: #eae4d5;
106
87
  }
package/web/i18n.js CHANGED
@@ -68,6 +68,9 @@ export const STRINGS = {
68
68
  themeLight: "Light",
69
69
  themeDark: "Dark",
70
70
  themeToggle: "Toggle theme",
71
+ layoutWide: "Wide",
72
+ layoutFocus: "Focus",
73
+ toggleWidth: "Toggle wide layout",
71
74
  copy: "Copy",
72
75
  copied: "Copied!",
73
76
  copyNote: "Copy text",
@@ -75,6 +78,10 @@ export const STRINGS = {
75
78
  export: "Export",
76
79
  exportTitle: "Export conversation",
77
80
  confirmShortcutHint: "Y to approve, N to decline",
81
+ starbuck: "Reference checks (Starbuck)",
82
+ starbuckHint: "Checks that cited works exist, match their citation and were not retracted. Sub-Sub restarts when you change this.",
83
+ starbuckOn: "Reference checks are on. Use /verify, or ask the researcher to check a manuscript's references.",
84
+ starbuckOff: "Reference checks are off.",
78
85
  },
79
86
  pt: {
80
87
  researcher: "Investigador",
@@ -142,6 +149,9 @@ export const STRINGS = {
142
149
  themeLight: "Claro",
143
150
  themeDark: "Escuro",
144
151
  themeToggle: "Mudar tema",
152
+ layoutWide: "Largo",
153
+ layoutFocus: "Foco",
154
+ toggleWidth: "Mudar largura do ecrã",
145
155
  copy: "Copiar",
146
156
  copied: "Copiado!",
147
157
  copyNote: "Copiar texto",
@@ -149,6 +159,10 @@ export const STRINGS = {
149
159
  export: "Exportar",
150
160
  exportTitle: "Exportar conversa",
151
161
  confirmShortcutHint: "Y para aprovar, N para recusar",
162
+ starbuck: "Verificação de referências (Starbuck)",
163
+ starbuckHint: "Verifica se as obras citadas existem, correspondem à citação e não foram retratadas. O Sub-Sub reinicia quando muda esta opção.",
164
+ starbuckOn: "A verificação de referências está ligada. Use /verify, ou peça ao investigador que verifique as referências de um manuscrito.",
165
+ starbuckOff: "A verificação de referências está desligada.",
152
166
  },
153
167
  };
154
168
 
@@ -156,11 +170,17 @@ export const STRINGS = {
156
170
  export const ACTIONS = {
157
171
  researcher: [
158
172
  { cmd: "subsub", send: true, en: "Library overview", pt: "Resumo da biblioteca" },
173
+ { cmd: "lit", fill: true, profiles: ["scholar", "author", "editor"], en: "Literature review", pt: "Revisão da literatura" },
174
+ { cmd: "compare", fill: true, profiles: ["scholar", "author", "editor"], en: "Compare sources", pt: "Comparar fontes" },
159
175
  { cmd: "lit-note", fill: true, en: "Literature note", pt: "Nota de leitura" },
160
176
  { cmd: "synthesis", fill: true, en: "Synthesis", pt: "Síntese" },
161
177
  { cmd: "gaps", fill: true, en: "What is missing", pt: "O que falta" },
162
178
  { cmd: "manuscript", fill: true, en: "Check a manuscript", pt: "Verificar um manuscrito" },
179
+ { cmd: "verify", fill: true, needs: "starbuck", en: "Check references", pt: "Verificar referências" },
180
+ { cmd: "review", fill: true, profiles: ["author", "editor"], en: "Review a manuscript", pt: "Rever um manuscrito" },
163
181
  { cmd: "alert", send: true, en: "New publications", pt: "Novas publicações" },
182
+ { cmd: "request-copy", fill: true, en: "Ask an author for a copy", pt: "Pedir uma cópia ao autor" },
183
+ { cmd: "digest", send: true, profiles: ["scholar", "author", "editor"], en: "Digest of the week", pt: "Resumo da semana" },
164
184
  ],
165
185
  librarian: [
166
186
  { cmd: "subsub", send: true, en: "Library overview", pt: "Resumo da biblioteca" },
@@ -228,6 +248,14 @@ export const TOOLS = {
228
248
  queue_imports: "queue imports",
229
249
  attach_note: "add a linked note",
230
250
  check_manuscript: "check a manuscript",
251
+ verify_check_manuscript: "check the references",
252
+ search_europepmc: "search Europe PMC",
253
+ search_multi: "search PubMed, Europe PMC and OpenAlex",
254
+ read_oa_fulltext: "read the open-access full text",
255
+ find_contact: "find the author's address",
256
+ verify_check_references: "check references",
257
+ verify_prepare_claims: "prepare the claim check",
258
+ verify_record_claims: "record the claim check",
231
259
  export_bibliography: "export a bibliography",
232
260
  read: "read a file",
233
261
  write: "write a file",
@@ -276,6 +304,14 @@ export const TOOLS = {
276
304
  queue_imports: "pôr importações na fila",
277
305
  attach_note: "acrescentar uma nota ligada",
278
306
  check_manuscript: "verificar um manuscrito",
307
+ verify_check_manuscript: "verificar as referências",
308
+ search_europepmc: "pesquisar na Europe PMC",
309
+ search_multi: "pesquisar na PubMed, Europe PMC e OpenAlex",
310
+ read_oa_fulltext: "ler o texto integral em acesso aberto",
311
+ find_contact: "procurar o endereço do autor",
312
+ verify_check_references: "verificar referências",
313
+ verify_prepare_claims: "preparar a verificação das afirmações",
314
+ verify_record_claims: "registar a verificação das afirmações",
279
315
  export_bibliography: "exportar uma bibliografia",
280
316
  read: "ler um ficheiro",
281
317
  write: "escrever um ficheiro",
package/web/icon.svg CHANGED
@@ -1 +1,12 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="6" fill="#12161b"/><text x="16" y="21.5" text-anchor="middle" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="14" font-weight="600" fill="#e0832a">SS</text></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <rect width="32" height="32" rx="5" fill="#f8f6f0"/>
3
+ <rect width="28" height="28" x="2" y="2" rx="4" fill="none" stroke="#211e1b" stroke-width="1.2"/>
4
+ <!-- Open Folio Book -->
5
+ <path d="M7 21.5V9A2 2 0 0 1 9 7h7v15H9a2 2 0 0 0-2 2v-2.5Z" fill="none" stroke="#211e1b" stroke-width="1.6" stroke-linejoin="round"/>
6
+ <path d="M25 21.5V9A2 2 0 0 0 23 7h-7v15h7a2 2 0 0 1 2 2v-2.5Z" fill="none" stroke="#211e1b" stroke-width="1.6" stroke-linejoin="round"/>
7
+ <line x1="16" y1="7" x2="16" y2="22" stroke="#211e1b" stroke-width="1.5"/>
8
+ <line x1="10" y1="11" x2="13" y2="11" stroke="#211e1b" stroke-width="1.2" stroke-linecap="round"/>
9
+ <line x1="10" y1="14" x2="13" y2="14" stroke="#211e1b" stroke-width="1.2" stroke-linecap="round"/>
10
+ <line x1="19" y1="11" x2="22" y2="11" stroke="#211e1b" stroke-width="1.2" stroke-linecap="round"/>
11
+ <line x1="19" y1="14" x2="22" y2="14" stroke="#211e1b" stroke-width="1.2" stroke-linecap="round"/>
12
+ </svg>
package/web/index.html CHANGED
@@ -6,6 +6,9 @@
6
6
  <meta name="color-scheme" content="light dark">
7
7
  <title>Sub-Sub</title>
8
8
  <link rel="icon" href="/icon.svg" type="image/svg+xml">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Courier+Prime:ital,wght@0,400;0,700;1,400&family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=JetBrains+Mono:wght@400;500;600&family=Newsreader:ital,opsz,wght@0,6..72,400..700;1,6..72,400..700&display=swap" rel="stylesheet">
9
12
  <link rel="stylesheet" href="/glauca.css">
10
13
  <link rel="stylesheet" href="/app.css">
11
14
  <script type="module" src="/app.js"></script>
@@ -13,8 +16,8 @@
13
16
  <body data-mode="researcher">
14
17
  <div class="app">
15
18
  <header class="top">
16
- <button type="button" class="ghost menu-btn" id="menu-btn" aria-controls="side" aria-expanded="false" data-i18n="menu">Menu</button>
17
- <div class="brand"><span class="mark" aria-hidden="true">SS</span> Sub-Sub</div>
19
+ <button type="button" class="ghost menu-btn" id="menu-btn" aria-controls="side" aria-expanded="true" title="Toggle catalog sidebar"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/></svg> <span data-i18n="menu">Menu</span></button>
20
+ <div class="brand"><span class="mark" aria-hidden="true"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1-2.5-2.5Z"/><path d="M6 6h10"/><path d="M6 10h10"/></svg></span> Sub-Sub</div>
18
21
  <div class="modes" role="group" aria-label="Mode">
19
22
  <button type="button" data-mode="researcher" aria-pressed="true" data-i18n="researcher">Researcher</button>
20
23
  <button type="button" data-mode="librarian" aria-pressed="false" data-i18n="librarian">Librarian</button>
@@ -30,6 +33,7 @@
30
33
  </select>
31
34
  </label>
32
35
  <button type="button" class="ghost model-btn" id="model-btn"></button>
36
+ <button type="button" class="ghost width-btn" id="width-btn" aria-label="Toggle wide layout" title="Toggle wide layout"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" x2="14" y1="3" y2="10"/><line x1="3" x2="10" y1="21" y2="14"/></svg> <span id="width-btn-text">Wide</span></button>
33
37
  <button type="button" class="ghost theme-btn" id="theme-btn" aria-label="Toggle theme" title="Toggle theme"></button>
34
38
  </div>
35
39
  </header>
@@ -52,6 +56,9 @@
52
56
  <button type="button" class="secondary theme-btn" id="theme-btn-side"></button>
53
57
  </label>
54
58
  </section>
59
+ <section class="addons">
60
+ <label class="check" title=""><input type="checkbox" id="starbuck-toggle"> <span data-i18n="starbuck">Reference checks (Starbuck)</span></label>
61
+ </section>
55
62
  <section>
56
63
  <h2 data-i18n="actions">Actions</h2>
57
64
  <div class="actions" id="actions"></div>