siddhm11 Claude Opus 5.5 commited on
Commit
0cb3a12
Β·
1 Parent(s): 0ef8102

test(e2e): briefing and Ask in the browser; fix an outline race

Browse files

The seeded world gains a cached briefing and two cached answers for
SatNav. New browser tests: the briefing's links and outline entries, a
suggested question echoed then answered with a cite, Enter sends while
Shift+Enter doesn't, a missing model says busy, the phone sheet opens and
closes on Escape with focus back on its button, and the lightbox asks about
its figure. Fixed on the way: the section observer could override the last
outline entry at the bottom of the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

app/static/paper.js CHANGED
@@ -247,8 +247,18 @@
247
  });
248
  }
249
 
 
 
 
 
 
 
 
 
250
  if (links.length && 'IntersectionObserver' in window) {
251
  var observer = new IntersectionObserver(function (entries) {
 
 
252
  entries.forEach(function (en) { if (en.isIntersecting) markActive(en.target.id); });
253
  }, { rootMargin: '-20% 0px -70% 0px' });
254
  /* A short last section never reaches that band, so at the bottom of the
@@ -259,10 +269,7 @@
259
  ticking = true;
260
  requestAnimationFrame(function () {
261
  ticking = false;
262
- var doc = document.documentElement;
263
- if (window.innerHeight + window.scrollY >= doc.scrollHeight - 4) {
264
- markActive(links[links.length - 1].getAttribute('data-sec'));
265
- }
266
  });
267
  }, { passive: true });
268
  function watch() {
 
247
  });
248
  }
249
 
250
+ function atBottom() {
251
+ return window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 4;
252
+ }
253
+ function lastVisibleLink() {
254
+ var shown = Array.prototype.filter.call(links, function (a) { return !a.hidden; });
255
+ return shown[shown.length - 1];
256
+ }
257
+
258
  if (links.length && 'IntersectionObserver' in window) {
259
  var observer = new IntersectionObserver(function (entries) {
260
+ /* At the bottom the last section wins, whatever crosses the band. */
261
+ if (atBottom()) { markActive(lastVisibleLink().getAttribute('data-sec')); return; }
262
  entries.forEach(function (en) { if (en.isIntersecting) markActive(en.target.id); });
263
  }, { rootMargin: '-20% 0px -70% 0px' });
264
  /* A short last section never reaches that band, so at the bottom of the
 
269
  ticking = true;
270
  requestAnimationFrame(function () {
271
  ticking = false;
272
+ if (atBottom()) markActive(lastVisibleLink().getAttribute('data-sec'));
 
 
 
273
  });
274
  }, { passive: true });
275
  function watch() {
scripts/seed_e2e.py CHANGED
@@ -25,7 +25,7 @@ values in .env.local can't leak in (load_dotenv never overrides a set name):
25
  DB_PATH=/tmp/e2e.db METADATA_SIDECAR_PATH=/tmp/e2e_sidecar.sqlite \\
26
  RESEARCHIT_ENV=test ARXIV_HTML_BASE=http://127.0.0.1:8799 \\
27
  TURSO_URL= TURSO_DB_TOKEN= QDRANT_URL= GROQ_API_KEY= TURSO_SYNC_DISABLED=1 \\
28
- PAPER_PAGE_V2=on FIGURES=on PAPER_SOURCES=on PORT=7861 python run.py
29
  """
30
  from __future__ import annotations
31
 
@@ -122,8 +122,31 @@ def build_site(site: Path, parsed: dict) -> int:
122
  return n
123
 
124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  async def seed_db(db_path: Path, parsed: dict) -> None:
126
- from app import config, db, groq_svc, paper_source_svc
 
127
  config.DB_PATH = db.DB_PATH = str(db_path)
128
  await db.init_db()
129
  for aid in PAPERS:
@@ -133,6 +156,23 @@ async def seed_db(db_path: Path, parsed: dict) -> None:
133
  abstracts[MATHS_ID] = MATHS_ABSTRACT
134
  for aid, text in SUMMARIES.items():
135
  await db.save_explanation(groq_svc.explain_cache_key(aid, abstracts[aid]), aid, text, "seed")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
 
138
  def main(argv=None) -> int:
 
25
  DB_PATH=/tmp/e2e.db METADATA_SIDECAR_PATH=/tmp/e2e_sidecar.sqlite \\
26
  RESEARCHIT_ENV=test ARXIV_HTML_BASE=http://127.0.0.1:8799 \\
27
  TURSO_URL= TURSO_DB_TOKEN= QDRANT_URL= GROQ_API_KEY= TURSO_SYNC_DISABLED=1 \\
28
+ PAPER_PAGE_V2=on FIGURES=on PAPER_SOURCES=on BRIEFING=on ASK=on PORT=7861 python run.py
29
  """
30
  from __future__ import annotations
31
 
 
122
  return n
123
 
124
 
125
+ SATNAV_BRIEFING = {
126
+ "things": [
127
+ {"lead": "118K episodes from satellite imagery.", "text": "Built across 59 scenes in 18 cities.",
128
+ "anchor": "abstract", "label": "Abstract"},
129
+ {"lead": "City-scale navigation is still hard.", "text": "Agents struggle on long routes.",
130
+ "anchor": "S1.p2", "label": "Β§1"},
131
+ {"lead": "Memory matters.", "text": "Removing long-term memory hurts.", "anchor": "S4.SS1.p1", "label": "Β§4.1"},
132
+ ],
133
+ "how": [{"name": "Pair the maps", "text": "Satellite images aligned with map data."},
134
+ {"name": "Write the tasks", "text": "Routes become instructions."},
135
+ {"name": "Navigate", "text": "The agent picks one of four actions."}],
136
+ "use": "Teams training drones that follow spoken directions.",
137
+ "dropped": [],
138
+ }
139
+ SATNAV_ANSWERS = {
140
+ "limits": {"kind": "answer", "answer": "The authors list three limitations, starting with discrete moves at a fixed altitude.",
141
+ "quotes": [], "cites": [{"anchor": "S6.p1", "label": "Β§6"}], "dropped": {"quotes": 0, "cites": 0}},
142
+ "new": {"kind": "answer", "answer": "A navigation benchmark built from satellite images instead of 3D models.",
143
+ "quotes": [], "cites": [{"anchor": "S1.p4", "label": "Β§1"}], "dropped": {"quotes": 0, "cites": 0}},
144
+ }
145
+
146
+
147
  async def seed_db(db_path: Path, parsed: dict) -> None:
148
+ import json as _json
149
+ from app import ask_svc, briefing_svc, config, db, groq_svc, paper_source_svc
150
  config.DB_PATH = db.DB_PATH = str(db_path)
151
  await db.init_db()
152
  for aid in PAPERS:
 
156
  abstracts[MATHS_ID] = MATHS_ABSTRACT
157
  for aid, text in SUMMARIES.items():
158
  await db.save_explanation(groq_svc.explain_cache_key(aid, abstracts[aid]), aid, text, "seed")
159
+ # A cached briefing and two cached suggested answers for SatNav, under the
160
+ # keys the server will compute with the default model chain.
161
+ version = parsed["2609.31507"].version
162
+ import aiosqlite
163
+ async with aiosqlite.connect(config.DB_PATH) as conn:
164
+ await conn.execute(
165
+ "INSERT INTO paper_briefings (cache_key, arxiv_id, status, briefing_json, provider, model, prompt_version) "
166
+ "VALUES (?, ?, 'ok', ?, 'seed', 'seed', ?)",
167
+ (briefing_svc.cache_key("2609.31507", version), "2609.31507", _json.dumps(SATNAV_BRIEFING),
168
+ briefing_svc.PROMPT_VERSION))
169
+ for key, answer in SATNAV_ANSWERS.items():
170
+ await conn.execute(
171
+ "INSERT INTO paper_answers (cache_key, arxiv_id, question_key, answer_json, model, prompt_version) "
172
+ "VALUES (?, ?, ?, ?, 'seed', ?)",
173
+ (ask_svc.cache_key("2609.31507", version, key), "2609.31507", key, _json.dumps(answer),
174
+ ask_svc.PROMPT_VERSION))
175
+ await conn.commit()
176
 
177
 
178
  def main(argv=None) -> int:
tests/test_e2e_paper.py CHANGED
@@ -124,7 +124,7 @@ def test_the_outline_marks_the_section_in_view(satnav):
124
  # At the very bottom the last section is the one in view, however short.
125
  satnav.evaluate("window.scrollTo(0, document.documentElement.scrollHeight)")
126
  satnav.wait_for_timeout(400)
127
- last = satnav.evaluate("[...document.querySelectorAll('.outline nav a')].at(-1).dataset.sec")
128
  assert active() == [last]
129
 
130
 
@@ -187,3 +187,71 @@ def test_a_paper_without_arxiv_html_shows_no_figure_sections(browser):
187
  ctx, page = open_page(browser, f"/p/{MATHS}")
188
  assert page.locator("#sec-fig").count() == 0 and page.locator("#sec-figs").count() == 0
189
  ctx.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  # At the very bottom the last section is the one in view, however short.
125
  satnav.evaluate("window.scrollTo(0, document.documentElement.scrollHeight)")
126
  satnav.wait_for_timeout(400)
127
+ last = satnav.evaluate("[...document.querySelectorAll('.outline nav a')].filter(a => !a.hidden).at(-1).dataset.sec")
128
  assert active() == [last]
129
 
130
 
 
187
  ctx, page = open_page(browser, f"/p/{MATHS}")
188
  assert page.locator("#sec-fig").count() == 0 and page.locator("#sec-figs").count() == 0
189
  ctx.close()
190
+
191
+
192
+ # ── briefing and Ask (server started with BRIEFING=on ASK=on) ────────────────
193
+
194
+ def _needs(page, selector, switch):
195
+ if page.locator(selector).count() == 0:
196
+ pytest.skip(f"server not started with {switch}=on")
197
+
198
+
199
+ def test_the_briefing_arrives_with_links_to_each_passage(satnav):
200
+ _needs(satnav, ".brief-slot, #sec-things", "BRIEFING")
201
+ satnav.wait_for_selector("#sec-things li")
202
+ assert satnav.locator("#sec-things li").count() == 3
203
+ href = satnav.get_attribute("#sec-things a.src", "href")
204
+ assert href == f"https://arxiv.org/html/{SATNAV}v1#abstract"
205
+ assert satnav.locator("#sec-how li").count() == 3 and satnav.is_visible("#sec-use")
206
+ # The outline's briefing links appear once the sections exist.
207
+ satnav.wait_for_function("!document.querySelector('.outline a[data-sec=\"sec-things\"]').hidden")
208
+
209
+
210
+ def test_a_suggested_question_is_echoed_then_answered_with_a_cite(satnav):
211
+ _needs(satnav, "#chat", "ASK")
212
+ satnav.click('[data-ask-key="limits"]')
213
+ satnav.wait_for_selector("#chat-log .msg-user")
214
+ assert satnav.inner_text("#chat-log .msg-user") == "What are the limitations?"
215
+ satnav.wait_for_selector("#chat-log .cite")
216
+ assert "three limitations" in satnav.inner_text("#chat-log")
217
+ assert satnav.get_attribute("#chat-log .cite", "href") == f"https://arxiv.org/html/{SATNAV}v1#S6.p1"
218
+ assert satnav.locator('[data-ask-key="limits"]').count() == 0 # the chip is used up
219
+
220
+
221
+ def test_a_typed_question_sends_on_enter_and_a_missing_model_says_busy(satnav):
222
+ _needs(satnav, "#chat", "ASK")
223
+ satnav.fill("#chat-input", "How many cities are there?")
224
+ satnav.press("#chat-input", "Enter")
225
+ satnav.wait_for_selector("#chat-log .msg-bot >> nth=1")
226
+ assert "busy right now" in satnav.inner_text("#chat-log")
227
+ assert satnav.input_value("#chat-input") == ""
228
+
229
+
230
+ def test_shift_enter_starts_a_new_line_instead_of_sending(satnav):
231
+ _needs(satnav, "#chat", "ASK")
232
+ satnav.fill("#chat-input", "First line")
233
+ satnav.press("#chat-input", "Shift+Enter")
234
+ satnav.wait_for_timeout(300)
235
+ assert satnav.locator("#chat-log .msg-user").count() == 0
236
+
237
+
238
+ def test_on_a_phone_ask_opens_as_a_sheet_and_closes_on_escape(browser):
239
+ ctx, page = open_page(browser, f"/p/{SATNAV}", width=390, height=844)
240
+ _needs(page, "#chat", "ASK")
241
+ assert not page.is_visible("#chat") and page.is_visible(".ask-fab")
242
+ page.click(".ask-fab")
243
+ assert page.is_visible("#chat") and page.evaluate("document.activeElement.id") == "chat-input"
244
+ page.keyboard.press("Escape")
245
+ assert not page.is_visible("#chat")
246
+ assert page.evaluate("document.activeElement.classList.contains('ask-fab')")
247
+ assert page.errors == []
248
+ ctx.close()
249
+
250
+
251
+ def test_ask_about_this_figure_asks_in_the_panel(satnav):
252
+ _needs(satnav, "#chat", "ASK")
253
+ satnav.click("#sec-fig [data-fig]")
254
+ satnav.wait_for_function("document.getElementById('lb').open")
255
+ satnav.click("#lb-ask")
256
+ satnav.wait_for_selector("#chat-log .msg-user")
257
+ assert satnav.inner_text("#chat-log .msg-user") == "Explain figure 1"