diff --git a/apps/web/public/home.html b/apps/web/public/home.html
index 879b6cb..c97982a 100644
--- a/apps/web/public/home.html
+++ b/apps/web/public/home.html
@@ -2,7 +2,7 @@
-
+
CorpusKit – open source research portal
@@ -82,7 +82,8 @@
position: fixed;
inset: 0 0 auto;
z-index: 12;
- height: 76px;
+ height: calc(76px + env(safe-area-inset-top));
+ padding-top: env(safe-area-inset-top);
border-bottom: 1px solid rgba(28, 28, 25, .15);
background: rgba(242, 239, 231, .91);
backdrop-filter: blur(16px);
@@ -140,7 +141,7 @@
.site-header.nav-dark .sign-in, .site-header.nav-blue .sign-in { border-left-color: rgba(255, 255, 255, .24); }
main { overflow: hidden; }
- #top, #workings, #run, #contribute { scroll-margin-top: 76px; }
+ #top, #workings, #run, #contribute { scroll-margin-top: calc(76px + env(safe-area-inset-top)); }
.hero {
width: var(--page);
@@ -625,6 +626,7 @@
.pv-mapcard { padding: .8rem; background: #fff; }
.pv-mapcard svg { display: block; width: 100%; height: auto; max-height: 520px; margin: 0 auto; }
+ .kg-mobile { display: none; }
.kg-legend-panel { fill: #fbfbf8; stroke: #deddd7; }
.kg-legend-title, .kg-legend-label, .kg-legend-count, .kg-node-label { font-family: var(--sans); fill: #5e5c56; }
.kg-legend-title { font-size: 9px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; }
@@ -690,6 +692,8 @@
.ways-caption .wc-t { display: inline; color: #c6c2b8; font-size: .9rem; line-height: 1.55; }
.ways-caption .wc-n, .ways-caption .wc-t { will-change: opacity, transform; }
+ .mobile-view-tabs { display: none; }
+
@media (min-width: 1900px) {
.ways-caption {
position: absolute;
@@ -768,6 +772,7 @@
@media (max-width: 900px) { .run-cols { grid-template-columns: 1fr; } }
.run-intro p + p { margin-top: 1.1rem; font-size: .98rem; }
.run-grid { width: var(--page); margin: 0 auto; display: grid; grid-template-columns: minmax(360px, .9fr) minmax(0, 1.1fr); gap: clamp(3rem, 7vw, 8rem); }
+ .run-grid > * { min-width: 0; }
.run-intro h2 { font-size: clamp(2.6rem, 4vw, 4.5rem); }
.run-intro p { max-width: 600px; margin: 2rem 0 0; color: #dce8f4; font-size: 1.08rem; }
@@ -886,11 +891,15 @@
@media (max-width: 680px) {
:root { --page: calc(100vw - 48px); }
body { font-size: 16px; }
- .site-header { height: 64px; }
+ .site-header {
+ position: absolute;
+ height: calc(64px + env(safe-area-inset-top));
+ }
.nav-links a:not(.sign-in) { display: none; }
.sign-in { padding-left: 1rem; }
.hero { min-height: 760px; padding-top: 8.2rem; padding-bottom: 2.5rem; }
.hero h1 { min-width: 0; font-size: min(13vw, 4.25rem); line-height: .92; text-wrap: wrap; }
+ .hero-thread { display: block; }
.hero-copy { min-width: 0; max-width: 100%; }
.hero-copy { margin-top: 2.4rem; }
.hero-note { margin-top: 4rem; align-items: flex-start; flex-direction: column; }
@@ -900,18 +909,58 @@
.section-head h2, .run-intro h2, .library-copy h2, .closing h2 { font-size: clamp(3.2rem, 15vw, 4.4rem); }
.closing h2 { font-size: clamp(3.4rem, 17vw, 5.2rem); line-height: .9; }
.run-intro h2 { font-size: clamp(2.4rem, 10.5vw, 3.1rem); }
- .portal-frame { margin-inline: -8px; }
- .portal-top { min-height: 64px; padding: .75rem; grid-template-columns: 1fr; }
+ .mobile-view-tabs {
+ display: grid;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ margin-bottom: 1.35rem;
+ border-bottom: 1px solid #4b4a45;
+ }
+ .mobile-view-tab {
+ min-width: 0;
+ padding: .78rem .1rem .72rem;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ background: transparent;
+ color: #aaa69d;
+ font-family: var(--display);
+ font-size: .8rem;
+ font-weight: 500;
+ letter-spacing: -.02em;
+ cursor: pointer;
+ }
+ .mobile-view-tab.active {
+ border-bottom-color: rgb(220, 232, 244);
+ color: var(--paper-soft);
+ font-weight: 600;
+ }
+ .ways-caption { padding-top: 0; border-top: 0; }
+ .ways-caption .wc-n { display: none; }
+ .portal-frame {
+ display: flex;
+ flex-direction: column;
+ height: clamp(520px, 78svh, 700px);
+ overflow: hidden;
+ }
+ .portal-top { display: none; }
.portal-name { display: none; }
.portal-search { font-size: .79rem; }
- .portal-nav { padding: .5rem; }
+ .portal-nav { display: none; }
.view-tab { min-width: auto; flex: 1; padding: .7rem .55rem; font-size: .8rem; justify-content: center; }
.view-tab::after { display: none; }
- .portal-main { padding: 1.15rem; }
- .portal-body { min-height: 700px; }
+ .portal-main { min-height: 0; padding: 2rem 1.15rem 1.15rem; overflow-y: auto; overscroll-behavior: contain; touch-action: pan-y; -webkit-overflow-scrolling: touch; }
+ .portal-body { flex: 1; min-height: 0; grid-template-rows: minmax(0, 1fr); }
.portal-view h3 { font-size: 2.3rem; margin-bottom: 1.3rem; }
.question-card { padding: 1rem; }
.answer p { font-size: 1rem; }
+ .confidence {
+ display: grid;
+ grid-template-columns: 36px minmax(0, 1fr);
+ gap: .3rem .75rem;
+ align-items: start;
+ }
+ .confidence::before { grid-column: 1; grid-row: 1; margin: .42rem 0 0; }
+ .confidence strong { grid-column: 2; grid-row: 1; }
+ .confidence span { grid-column: 2; grid-row: 2; }
.passage-drawer { min-height: 220px; }
.no-evidence { font-size: .84rem; }
.result { grid-template-columns: 46px minmax(0, 1fr); gap: .75rem; }
@@ -920,6 +969,43 @@
.resource-card { min-height: 170px; }
.resource-card:nth-child(n+3) { display: none; }
.map-canvas { min-height: 480px; }
+ .pv-mapcard { padding: 0; }
+ .pv-mapcard .kg-desktop { display: none; }
+ .pv-mapcard .kg-mobile { display: block; max-height: none; }
+ .kg-mobile .kg-node-label { font-size: 11px; stroke-width: 4px; }
+ .kg-mobile .kg-node-label.key { font-size: 12px; }
+ .kg-mobile .kg-cluster-label { font-size: 9px; }
+ .pv-controls {
+ flex-direction: column;
+ align-items: stretch;
+ gap: .75rem;
+ margin-bottom: 1rem;
+ }
+ .pv-querybar { flex-basis: auto; width: 100%; padding: .78rem .85rem; font-size: .86rem; }
+ .sr-modes {
+ display: grid;
+ grid-auto-flow: column;
+ grid-auto-columns: minmax(0, 1fr);
+ width: 100%;
+ gap: .2rem;
+ }
+ .sr-modes span {
+ min-width: 0;
+ padding: .38rem .2rem;
+ font-size: .64rem;
+ text-align: center;
+ white-space: nowrap;
+ }
+ .pv-chiprow { width: 100%; gap: .35rem; }
+ .chip { padding: .3rem .58rem; font-size: .69rem; }
+ .pv-chiprow .pv-meta,
+ .pv-controls > .pv-meta {
+ flex-basis: 100%;
+ width: 100%;
+ margin: .45rem 0 0;
+ font-size: .78rem;
+ text-align: left;
+ }
.node-a { left: 4%; top: 19%; }
.node-b { right: 3%; top: 25%; }
.node-c { right: 5%; bottom: 16%; }
@@ -935,13 +1021,22 @@
.blue-note { width: 80px; right: 0; font-size: .64rem; }
.run-section { padding: 5.5rem 0; }
.run-grid { gap: 3rem; }
+ .code-card { width: 100%; min-width: 0; max-width: 100%; overflow: hidden; }
+ .code-card pre {
+ max-width: 100%;
+ overflow-x: hidden;
+ overflow-wrap: anywhere;
+ white-space: pre-wrap;
+ }
+ .terminal-line { max-width: 100%; }
.requirement { grid-template-columns: 1fr; gap: .25rem; }
.stewardship { padding: 6rem 0; }
.stewardship blockquote { font-size: clamp(3rem, 14vw, 4.5rem); }
.closing { min-height: auto; padding-top: 5.5rem; }
+ .closing-thread { display: block; }
.next-steps { grid-template-columns: 1fr; margin-top: 5rem; }
.next-step { min-height: 175px; }
- .site-footer { flex-direction: column; gap: .4rem; }
+ .site-footer { flex-direction: column; gap: .4rem; padding-bottom: calc(2rem + env(safe-area-inset-bottom)); }
}
@media (prefers-reduced-motion: reduce) {
@@ -978,9 +1073,9 @@
.psig-thread {
position: absolute; inset: 0; z-index: 4;
pointer-events: none; overflow: visible;
- display: none;
+ display: none; width: 100%; height: 100%;
}
- .psig-thread path { fill: none; stroke: var(--blue); stroke-width: 1.5; }
+ .psig-thread path { fill: none; stroke: var(--blue); stroke-width: 1.5; stroke-linecap: round; }
.psig-thread circle { fill: var(--blue); }
@media (min-width: 1024px) {
.psig-thread { display: block; }
@@ -1006,6 +1101,11 @@
.psig-steps li.is-active .n { color: var(--blue); }
@media (max-width: 1023px) {
.psig-stage { min-height: 0; padding-block: clamp(4rem, 9vw, 6rem); }
+ .psig-head { margin-bottom: 1.6rem; }
+ .psig-thread { display: block; }
+ .psig-left { display: flex; flex-direction: column; }
+ .psig-answer { order: 2; }
+ .psig-steps { order: 1; margin: 0 0 2.2rem; }
.psig-steps li, .psig-steps .n { color: var(--ink); }
.psig-steps .n { color: var(--blue); }
}
@@ -1083,6 +1183,13 @@ Inside a CorpusKit portal
+
+ Ask
+ Search
+ Library
+ Map
+ Manage
+
Ask Enter a question in Ask. CorpusKit searches the collection and returns an answer with citations, sources and a confidence rating. Deep research is available when a broad question needs to be broken into smaller ones.
@@ -1201,7 +1308,7 @@
What affected seagrass recovery after the 2022 floods?
Select a connection to see the resources behind it
-
+
Entity types
Research topic 26
@@ -1259,6 +1366,70 @@ What affected seagrass recovery after the 2022 floods?
Zostera muelleri
Halophila ovalis
+
+
+
+
+ Topic
+ Habitat
+ Pressure
+ Species
+
+
+
+
+
+
+ RESEARCH TOPICS
+ HABITATS
+ PRESSURES
+ SPECIES
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ water quality
+ monitoring
+ winter turbidity
+ light availability
+ recovery
+ seagrass
+ estuary
+ coastal habitat
+ 2022 floods
+ sediment
+ runoff
+ Zostera muelleri
+
+ 54 entities · 87 links
+
@@ -1287,7 +1458,7 @@ Check the answer against the research
CorpusKit links claims in an answer to the source passages that support them. Quality scores show how relevant the retrieved material is, how closely the answer follows it and whether enough context was found.
-
+
Seagrass recovered more slowly at sites where winter turbidity remained above the long-term median. At those sites, light at the canopy fell below the minimum required by Zostera muelleri on 63 per cent of monitored days.2
1 Choose a claim to check
@@ -1409,7 +1580,8 @@ Be a part of
const { gsap, ScrollTrigger, ScrollSmoother } = window;
(function () {
- const tabs = Array.from(document.querySelectorAll('.view-tab'));
+ const tabs = Array.from(document.querySelectorAll('.view-tab, .mobile-view-tab'));
+ const viewNames = ['ask', 'search', 'library', 'map', 'manage'];
const views = Array.from(document.querySelectorAll('.portal-view'));
const citations = Array.from(document.querySelectorAll('.citation'));
const passage = document.getElementById('passage');
@@ -1440,6 +1612,9 @@ Be a part of Be a part of Be a part of Be a part of citationBounds.bottom + lineHeight;
svg.setAttribute('viewBox', '0 0 ' + bounds.width + ' ' + bounds.height);
- path.setAttribute('d', 'M' + x1 + ' ' + y1 + ' C ' + (x1 + bend) + ' ' + y1 + ', ' + (x2 - bend) + ' ' + y2 + ', ' + x2 + ' ' + y2);
+ if (stacked) {
+ x2 = descriptionBounds.right - bounds.left - 32;
+ y2 = descriptionBounds.top - bounds.top - 10;
+ path.setAttribute('d', 'M' + x1 + ' ' + y1 + ' Q ' + x2 + ' ' + y1 + ', ' + x2 + ' ' + y2);
+ } else {
+ var distance = Math.max(60, x2 - x1);
+ var bend = Math.min(110, distance * .5);
+ path.setAttribute('d', 'M' + x1 + ' ' + y1 + ' C ' + (x1 + bend) + ' ' + y1 + ', ' + (x2 - bend) + ' ' + y2 + ', ' + x2 + ' ' + y2);
+ }
dot.setAttribute('cx', x2);
dot.setAttribute('cy', y2);
@@ -1658,6 +1848,7 @@ Be a part of Be a part of Be a part of Be a part of {
return heading ? heading.scrollWidth - heading.clientWidth : 10_000
})(),
pageInset: document.querySelector('.hero')?.getBoundingClientRect().left ?? 0,
+ heroThread: (() => {
+ const thread = document.querySelector('#heroThread')
+ const path = document.querySelector('#heroThreadPath')
+ return {
+ display: thread ? getComputedStyle(thread).display : 'missing',
+ length: path?.getTotalLength() ?? 0,
+ }
+ })(),
+ closingThread: (() => {
+ const thread = document.querySelector('#closingThread')
+ const path = document.querySelector('#closingThreadPath')
+ const dot = document.querySelector('#closingThreadDot')
+ const cards = document.querySelector('.next-steps')
+ const threadBounds = thread?.getBoundingClientRect()
+ const cardsBounds = cards?.getBoundingClientRect()
+ const cardsCentre = threadBounds && cardsBounds
+ ? (cardsBounds.left + cardsBounds.right) / 2 - threadBounds.left
+ : 0
+ return {
+ display: thread ? getComputedStyle(thread).display : 'missing',
+ length: path?.getTotalLength() ?? 0,
+ targetOffset: Math.abs(Number(dot?.getAttribute('cx') ?? 10_000) - cardsCentre),
+ }
+ })(),
+ evidenceFlow: (() => {
+ const description = document.querySelector('.psig-head p')
+ const steps = document.querySelector('.psig-steps')
+ const answer = document.querySelector('.psig-answer')
+ const source = document.querySelector('#psigSource')
+ const thread = document.querySelector('#psigThread')
+ const path = document.querySelector('#psigThreadPath')
+ return {
+ descriptionBottom: description?.getBoundingClientRect().bottom ?? 0,
+ stepsTop: steps?.getBoundingClientRect().top ?? 0,
+ stepsBottom: steps?.getBoundingClientRect().bottom ?? 0,
+ answerTop: answer?.getBoundingClientRect().top ?? 0,
+ answerBottom: answer?.getBoundingClientRect().bottom ?? 0,
+ sourceTop: source?.getBoundingClientRect().top ?? 0,
+ threadDisplay: thread ? getComputedStyle(thread).display : 'missing',
+ threadLength: path?.getTotalLength() ?? 0,
+ }
+ })(),
+ portalInset: (() => {
+ const frame = document.querySelector('.portal-frame')
+ if (!frame) return { left: 0, right: 0 }
+ const bounds = frame.getBoundingClientRect()
+ return { left: bounds.left, right: innerWidth - bounds.right }
+ })(),
+ portalScreen: (() => {
+ const frame = document.querySelector('.portal-frame')
+ const main = document.querySelector('.portal-main')
+ return {
+ height: frame?.getBoundingClientRect().height ?? 0,
+ contentHeight: main?.clientHeight ?? 0,
+ scrollHeight: main?.scrollHeight ?? 0,
+ overflowY: main ? getComputedStyle(main).overflowY : 'missing',
+ touchAction: main ? getComputedStyle(main).touchAction : 'missing',
+ pageSmootherActive: Boolean(
+ (window as unknown as { ScrollSmoother?: { get: () => unknown } }).ScrollSmoother
+ ?.get(),
+ ),
+ }
+ })(),
+ mobileTabs: {
+ display:
+ getComputedStyle(document.querySelector('.mobile-view-tabs')!).display,
+ portalDisplay:
+ getComputedStyle(document.querySelector('.portal-nav')!).display,
+ portalTopDisplay:
+ getComputedStyle(document.querySelector('.portal-top')!).display,
+ },
+ headerPosition:
+ getComputedStyle(document.querySelector('.site-header')!).position,
nextSteps: document.querySelectorAll('.next-step').length,
}))
expect(state.horizontalOverflow).toBeLessThanOrEqual(0)
expect(state.heading).toContain('Put your organisation’s')
expect(state.headingOverflow).toBeLessThanOrEqual(0)
expect(state.pageInset).toBeGreaterThanOrEqual(24)
+ expect(state.heroThread.display).toBe('block')
+ expect(state.heroThread.length).toBeGreaterThan(0)
+ expect(state.closingThread.display).toBe('block')
+ expect(state.closingThread.length).toBeGreaterThan(0)
+ expect(state.closingThread.targetOffset).toBeLessThanOrEqual(1)
+ expect(state.evidenceFlow.descriptionBottom).toBeLessThan(state.evidenceFlow.stepsTop)
+ expect(state.evidenceFlow.stepsBottom).toBeLessThan(state.evidenceFlow.answerTop)
+ expect(state.evidenceFlow.answerBottom).toBeLessThan(state.evidenceFlow.sourceTop)
+ expect(state.evidenceFlow.threadDisplay).toBe('block')
+ expect(state.evidenceFlow.threadLength).toBeGreaterThan(0)
+ expect(state.portalInset.left).toBeGreaterThanOrEqual(24)
+ expect(state.portalInset.right).toBeGreaterThanOrEqual(24)
+ expect(state.portalScreen.height).toBeLessThanOrEqual(844 * 0.78 + 1)
+ expect(state.portalScreen.scrollHeight).toBeGreaterThan(state.portalScreen.contentHeight)
+ expect(state.portalScreen.overflowY).toBe('auto')
+ expect(state.portalScreen.touchAction).toBe('pan-y')
+ expect(state.portalScreen.pageSmootherActive).toBe(false)
+ expect(state.mobileTabs.display).toBe('grid')
+ expect(state.mobileTabs.portalDisplay).toBe('none')
+ expect(state.mobileTabs.portalTopDisplay).toBe('none')
+ expect(state.headerPosition).toBe('absolute')
expect(state.nextSteps).toBe(3)
+
+ await page.evaluate(() => {
+ document.querySelector('#mobile-tab-search')?.click()
+ })
+ await page.evaluate(async () => await new Promise((resolve) => setTimeout(resolve, 750)))
+ const selectedFeature = await page.evaluate(() => ({
+ tab: document.querySelector('.mobile-view-tab.active')?.textContent?.trim(),
+ caption: document.getElementById('waysCaptionText')?.textContent,
+ view: document.querySelector('.portal-view.active')?.id,
+ }))
+ expect(selectedFeature.tab).toBe('Search')
+ expect(selectedFeature.caption).toContain('Search for words, topics or document kinds')
+ expect(selectedFeature.view).toBe('view-search')
+
+ const searchControls = await page.evaluate(() => ({
+ direction:
+ getComputedStyle(document.querySelector('#view-search .pv-controls')!)
+ .flexDirection,
+ modesDisplay:
+ getComputedStyle(document.querySelector('#view-search .sr-modes')!).display,
+ modesWidth:
+ document.querySelector('#view-search .sr-modes')?.getBoundingClientRect()
+ .width ?? 0,
+ statusAlign:
+ getComputedStyle(document.querySelector('#view-search .pv-meta')!).textAlign,
+ }))
+ expect(searchControls.direction).toBe('column')
+ expect(searchControls.modesDisplay).toBe('grid')
+ expect(searchControls.modesWidth).toBeGreaterThan(250)
+ expect(searchControls.statusAlign).toBe('left')
+
+ const internalScrollTop = await page.evaluate(() => {
+ const main = document.querySelector('.portal-main')
+ if (!main) return 0
+ main.scrollTop = 120
+ return main.scrollTop
+ })
+ expect(internalScrollTop).toBeGreaterThan(0)
+
+ await page.evaluate(() => {
+ document.querySelector('#mobile-tab-map')?.click()
+ })
+ await page.evaluate(async () => await new Promise((resolve) => setTimeout(resolve, 750)))
+ const mobileMap = await page.evaluate(() => ({
+ view: document.querySelector('.portal-view.active')?.id,
+ desktopDisplay:
+ getComputedStyle(document.querySelector('.kg-desktop')!).display,
+ mobileDisplay:
+ getComputedStyle(document.querySelector('.kg-mobile')!).display,
+ scrollTop: document.querySelector('.portal-main')?.scrollTop,
+ label: document.querySelector('.kg-mobile')?.getAttribute('aria-label'),
+ }))
+ expect(mobileMap.view).toBe('view-map')
+ expect(mobileMap.desktopDisplay).toBe('none')
+ expect(mobileMap.mobileDisplay).toBe('block')
+ expect(mobileMap.scrollTop).toBe(0)
+ expect(mobileMap.label).toContain('mobile knowledge graph')
+
+ const mapControls = await page.evaluate(() => ({
+ direction: getComputedStyle(document.querySelector('#view-map .pv-controls')!)
+ .flexDirection,
+ statusAlign:
+ getComputedStyle(document.querySelector('#view-map .pv-meta')!).textAlign,
+ }))
+ expect(mapControls.direction).toBe('column')
+ expect(mapControls.statusAlign).toBe('left')
+
+ await page.evaluate(() => {
+ document.querySelector('#mobile-tab-library')?.click()
+ })
+ await page.evaluate(async () => await new Promise((resolve) => setTimeout(resolve, 750)))
+ const libraryControls = await page.evaluate(() => ({
+ direction:
+ getComputedStyle(document.querySelector('#view-library .pv-controls')!)
+ .flexDirection,
+ statusAlign:
+ getComputedStyle(document.querySelector('#view-library .pv-meta')!)
+ .textAlign,
+ }))
+ expect(libraryControls.direction).toBe('column')
+ expect(libraryControls.statusAlign).toBe('left')
+
+ await page.evaluate(() => {
+ document.querySelector('#mobile-tab-ask')?.click()
+ })
+ await page.evaluate(async () => await new Promise((resolve) => setTimeout(resolve, 750)))
+ const confidence = await page.evaluate(() => {
+ const confidence = document.querySelector('#view-ask .confidence')
+ const label = confidence?.querySelector('strong')
+ const detail = confidence?.querySelector('span')
+ return {
+ display: confidence ? getComputedStyle(confidence).display : 'missing',
+ labelTop: label?.getBoundingClientRect().top ?? 0,
+ detailTop: detail?.getBoundingClientRect().top ?? 0,
+ }
+ })
+ expect(confidence.display).toBe('grid')
+ expect(confidence.detailTop).toBeGreaterThan(confidence.labelTop)
+
+ await page.evaluate(() => {
+ const commands = [
+ 'git clone https://github.com/noicework/kb.git',
+ 'cd kb',
+ 'cp .env.example .env',
+ 'deno task provision',
+ 'deno task dev',
+ ]
+ document.querySelectorAll('.typed-command').forEach((command, index) => {
+ command.textContent = commands[index] ?? ''
+ })
+ const comment = document.querySelector('.typed-comment')
+ if (comment) comment.textContent = ' # knowledge service credentials'
+ })
+ const completedTerminal = await page.evaluate(() => {
+ const card = document.querySelector('.code-card')
+ const pre = card?.querySelector('pre')
+ const bounds = card?.getBoundingClientRect()
+ return {
+ horizontalOverflow: document.documentElement.scrollWidth - innerWidth,
+ left: bounds?.left ?? 0,
+ rightInset: bounds ? innerWidth - bounds.right : 0,
+ preOverflow: pre ? pre.scrollWidth - pre.clientWidth : 10_000,
+ command: card?.querySelector('.terminal-line')?.textContent,
+ }
+ })
+ expect(completedTerminal.horizontalOverflow).toBeLessThanOrEqual(0)
+ expect(completedTerminal.left).toBeGreaterThanOrEqual(24)
+ expect(completedTerminal.rightInset).toBeGreaterThanOrEqual(24)
+ expect(completedTerminal.preOverflow).toBeLessThanOrEqual(0)
+ expect(completedTerminal.command).toContain('https://github.com/noicework/kb.git')
} finally {
await page.close()
}