Skip to content

FE-376: Add a DOCX preview to the entity editor - #9995

Open
0xRezaa wants to merge 18 commits into
reza/fe-1898-add-an-end-to-end-test-for-the-pdf-previewfrom
reza/fe-376-docx-file-previewing-in-hash
Open

0xRezaa wants to merge 18 commits into
reza/fe-1898-add-an-end-to-end-test-for-the-pdf-previewfrom
reza/fe-376-docx-file-previewing-in-hash

Conversation

@0xRezaa

@0xRezaa 0xRezaa commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

🌟 Purpose

The entity editor's File Preview now shows uploaded .docx files as pages, with the PDF preview's paging, zoom, full screen, search, and select-and-copy. Until now a .docx showed only a file icon. The preview draws the file in the browser, so the file stays in HASH. Closes FE-376.

The renderer is @stll/folio-react (folio below), used read-only. It was chosen by an evaluation of browser DOCX libraries. Each candidate had to pass fixed criteria (runs in the browser, licence, download size, speed, no outbound requests), and those that passed were scored on layout fidelity, backing, integration effort and speed. Three passed: eigenpal docx-editor, folio and @silurus/ooxml. folio was chosen because readers can select and copy text, which eigenpal's view mode blocks, and because it stays usable with the JavaScript JIT blocked. The preview was tested in Chrome, Chrome 122, Edge, and Edge with the JIT blocked. With the JIT blocked, the first page of a real document takes up to 2.6 s, against about 1.2 s in Chrome.

🔍 Changes

  • docx-preview/folio-document.tsx is the only file that imports folio. The rest of the preview uses it through DocxDocumentControls.
  • folio depends on newer ProseMirror and yjs packages than HASH's block editor, which pins older versions. A webpack rule in next.config.js resolves every ProseMirror and yjs import inside folio's packages to folio's own copies. The build fails if folio would share a copy with HASH's editor.
  • Root resolutions pin the ^1.0.0 and >=1.0.0 ranges of prosemirror-state to 1.4.3. Like the existing prosemirror-model and prosemirror-view resolutions, they keep the block editor on one copy. HASH pins prosemirror-state at 1.4.3, and without the resolutions yarn dedupe --strategy highest moves the ranges that HASH's other ProseMirror packages use onto folio's 1.4.4.
  • .yarnrc.yml adds y-protocols to @stll/folio-core, which imports it through y-prosemirror without declaring it. Two .d.ts files make folio's messages and ai-edits subpaths visible to the frontend's moduleResolution: "node", and @stll/folio-core is a direct dependency because they import it.
  • @stll/folio-react 0.26.0, @stll/folio-core 0.58.5 and @stll/docx-core 0.36.2 are younger than the 7-day minimum package age (npmMinimalAgeGate), so npmPreapprovedPackages in .yarnrc.yml admits exactly these versions. I read through folio's commits from 2026-09-27 up to 0.26.0 first. They are signed and come from its one maintainer and its release bot. The npm packages are built from the tagged commits, and they add no install scripts, network calls or known advisories.
  • The PDF preview's search bar becomes FilePreviewSearch, shared by both previews, and both previews fetch the file with fetchPresignedFileUrl. The PDF preview behaves as before.
  • When folio reports an error before it draws any page, as with a corrupt file, the section shows the file icon that other files get. An error after pages are showing goes to Sentry and the pages stay. Download is available in both cases.

file-preview.spec.ts gains DOCX tests next to the PDF tests from #9972, one per behaviour:

  • Paging: the "Next page" and "Previous page" buttons move between pages.
  • Zoom: the "Zoom in" button makes the page wider.
  • Search: a word on pages 1 and 3 shows "1 of 2". The "Next result" button then shows "2 of 2" and scrolls to page 3.
  • Search without results: a search with no hits clears the previous hit's mark.
  • Read-only: typing, Enter, Backspace and Delete leave the page text unchanged.
  • Copy: dragging across the page 1 heading and copying puts exactly that heading on the clipboard.
  • Unsafe content: a document with a javascript: link, a data: link, a remote picture and an HTML altChunk holding a script renders without scripts, iframes, javascript: or data: links, or event-handler attributes. Clicking its links opens no dialog, and the page requests nothing outside localhost.
  • .doc: a .doc file keeps the icon view.
  • Unreadable .docx: a corrupt file and a file with a bookmark folio rejects both show the icon view and keep Download.

generate-test-docx.ts writes the documents in the test, so the PR adds no binary file.

⚠️ Known issues

  • folio cannot open some documents with repeated bookmark ids. The section then shows the file icon instead of a preview.
  • Tracked changes and comment highlights show. folio has no option to hide them.
  • DOCX search marks only the current hit, where the PDF preview marks every hit, and it does not search headers, footers or footnotes.
  • .doc files keep the icon view.
  • folio does not draw charts, shape groups, or EMF and WMF pictures.

🚫 Blocked by

@0xRezaa
0xRezaa added this pull request to stack #9996 October 12, 2026 07:19
@vercel

vercel Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hash Ready Ready Preview Oct 13, 2026 2:26pm UTC
3 Skipped Deployments
Project Deployment Actions Updated
hashdotdesign-tokens Ignored Ignored Preview Oct 13, 2026 2:26pm UTC
petrinaut Skipped Skipped Oct 13, 2026 2:26pm UTC
petrinaut-docs Skipped Skipped Oct 13, 2026 2:26pm UTC

Request Review

@github-actions github-actions Bot added area/deps Relates to third-party dependencies (area) area/apps > hash* Affects HASH (a `hash-*` app) type/eng > frontend Owned by the @frontend team area/tests New or updated tests area/tests > playwright New or updated Playwright tests area/apps labels Oct 12, 2026
@0xRezaa 0xRezaa self-assigned this Oct 12, 2026
@CLAassistant

CLAassistant commented Oct 12, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@codspeed

codspeed Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Merging this PR will improve performance by 10.38%

⚠️ 6 benchmarks measured no execution time

Nothing ran under measurement, usually because the compiler removed the code under test. These results are not comparable, so they count as unchanged.

Preventing compiler optimizations

⚠️ 1 benchmark spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 97 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
⚡ repeated[1000] 2.4 ms 2.2 ms +10.38%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing reza/fe-376-docx-file-previewing-in-hash (103d35f) with main (ece3dd1)1

Open in CodSpeed

Footnotes

  1. No successful run was found on reza/fe-1898-add-an-end-to-end-test-for-the-pdf-preview (d03bb52) during the generation of this report, so main (ece3dd1) was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

Comment thread apps/hash-frontend/src/pages/shared/docx-preview/folio-document.tsx Dismissed
@cursor

cursor Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Large frontend dependency surface (folio/ProseMirror isolation) and untrusted DOCX rendering; mitigated by read-only folio, webpack duplication guards, and hostile-document e2e tests.

Overview
Adds in-browser DOCX preview to the entity editor File Preview section using read-only @stll/folio-react, with paging, zoom, full screen, search, and copy—matching much of the existing PDF experience.

Integration and build: file-preview-section dynamically loads DocxPreview for .docx files (mime or extension) and falls back to the file icon when folio cannot render. A new webpack rule (folioSingleProseMirrorRule) aliases ProseMirror/yjs inside folio’s tree so they do not collide with HASH’s block editor; root prosemirror-state resolutions and Yarn npmPreapprovedPackages support the new @stll/* deps.

Refactors: PDF search UI moves to shared FilePreviewSearch; presigned download logic moves to fetchPresignedFileUrl (used by both previews). pdf-search exposes useFindSearchHits only.

Tests: Playwright file-preview.spec.ts splits PDF/DOCX suites, adds DOCX fixture builders (generate-test-docx.ts), hostile-document safety checks, and per-test tolerable console messages.

Reviewed by Cursor Bugbot for commit 103d35f. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread package.json
@0xRezaa

0xRezaa commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

The y-protocols entry in .yarnrc.yml is a workaround for folio: y-prosemirror imports its peer y-protocols, and @stll/folio-core doesn't provide it.

hash/.yarnrc.yml

Lines 31 to 34 in 2314834

# `y-prosemirror` imports its peer `y-protocols`, which folio-core does not provide.
"@stll/folio-core@*":
dependencies:
"y-protocols": "1.0.7"

stella/folio#1679 fixes this upstream by adding y-protocols to folio-core's dependencies. It's merged but not yet published. Once the patch release is on npm, we can bump @stll/folio-core to it, add that exact version to npmPreapprovedPackages in place of 0.58.5 so it passes the 7-day age gate, and remove this packageExtensions entry.

@0xRezaa

0xRezaa commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up to #9995 (comment): 20b203c moves the workaround out of the root .yarnrc.yml and into apps/hash-frontend/package.json as a direct y-protocols dependency, so it stays scoped to the frontend.

y-prosemirror finds the hoisted node_modules/y-protocols at runtime, and next build passes. Yarn now warns that folio-core doesn't provide y-protocols to y-prosemirror, and that the frontend doesn't provide yjs to y-protocols. Both are YN0002 warnings, which CI's yarn install --immutable and lockfile checks don't fail on. yjs stays out of the frontend's dependencies because next.config.js refuses a ProseMirror or Yjs package that the frontend and folio resolve to the same copy.

When we bump @stll/folio-core to the release with stella/folio#1679, remove y-protocols from the frontend's dependencies.

The File Preview section renders .docx files with @stll/folio-react in
read-only mode, with page buttons, zoom, full screen and search, loaded
only when a DOCX file is opened. The PDF and DOCX previews share the
presigned-URL fetch.

folio needs newer ProseMirror packages than HASH's editor, so a webpack
rule resolves folio's tree to one copy of each. `.yarnrc.yml` adds
folio-core's missing `y-protocols` dependency.

The file-preview Playwright spec gains DOCX cases: paging, zoom and
search; read-only; copy; a hostile document; `.doc` and corrupt files.
Move `pdf-preview/pdf-search.tsx` to `file-preview-search.tsx`, next to
the PDF and DOCX previews, so the DOCX preview can use the same bar.
Move the PDF text extraction and hit finding into
`pdf-preview/pdf-search-hits.ts`. `FilePreviewSearch` keeps the bar and
its input state, and takes the hit count, the selected hit's index and
callbacks to search and to select a hit, so it does not depend on
pdf.js.
The DOCX preview now searches with `FilePreviewSearch`, like the PDF
preview, instead of opening folio's own find dialog. `FolioDocument`
finds the hits in folio's block snapshot and shows each one with
`showInDocument`, which selects the exact range and scrolls to it.
`keyboardShortcuts="none"` keeps folio's dialog closed.

`@stll/folio-core` becomes a direct dependency, at the version
`@stll/folio-react` uses, because the frontend now relies on its
`ai-edits` types.
folio brings prosemirror-state 1.4.4 and prosemirror-transform 1.12.2.
`yarn dedupe --strategy highest` then moves the `^1.0.0` ranges that
HASH's prosemirror-view, -keymap and -commands use onto them, while
HASH's own code stays on prosemirror-state 1.4.3. Each of those packages
would get its own nested copy, and HASH's editor would mix copies.

Pin those ranges to the versions they resolve to today, as the root
`resolutions` already do for prosemirror-model and prosemirror-view, and
dedupe the rest (reselect, valibot, @floating-ui/react-dom).

The webpack rule that keeps folio on its own ProseMirror copies now fails
the build if one of those copies is the frontend's own, because aliasing
its imports would change the frontend's editor too.
The sidebar's entity list can still be loading when the check runs, and its spinners are not the preview's.
The DOCX tests that share a three-page document now upload it in a
`beforeEach`, as the PDF tests do, so paging, zoom, search, read-only
and copy each fail on their own.
folio-core moves to 0.58.5, the version folio-react 0.26.0 uses. Both,
and `@stll/docx-core` 0.36.2, are younger than the 7-day age gate, so
`npmPreapprovedPackages` admits exactly these versions. folio's commits
up to 0.26.0 were read through before the update.
The `@stll/folio-react/messages` declaration re-exports folio-core's own
types instead of a hand-written signature, so it returns `FolioMessages`
and follows folio when it changes.
`showHit` marks a hit by selecting it, and a later search with no hits
left that selection on screen. The search now collapses it.
An error before folio lays out any page, as with a corrupt file or one
whose bookmarks folio rejects, now falls back to the icon view other
files get. An error after pages are showing is reported to Sentry and
leaves them in place. Download stays available either way.
…heck

The DOCX preview's engine logs a `ProseMirrorDocumentValidationError` when
it refuses a document, and the preview then shows the file's icon. The
test that opens such a document on purpose failed on that console message.
The generator wrote the ZIP headers byte by byte, which a reviewer could
only check against the ZIP specification. jszip is already in the lockfile
through folio, so the generator now hands it the DOCX parts instead. The
parts are unchanged, and the generators return a promise.

The page size and margins move into a named constant with a comment on
their units.
The shared list of tolerable console messages let every e2e test log a
`ProseMirrorDocumentValidationError`. The console check now also reads a
`tolerableConsoleMessages` option, and the test that opens a DOCX folio
cannot draw sets it with `test.use`. Every other test still fails on that
message.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fbe9297. Configure here.

Comment thread apps/hash-frontend/src/pages/shared/docx-preview.tsx
const isDocx =
(mimeType === docxMimeType ||
!!fileName?.toLowerCase().endsWith(".docx")) &&
unavailableDocxUrl !== fileUrl;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failed preview never retries same URL

Medium Severity

unavailableDocxUrl is never cleared, and handleError treats download failures like a corrupt file. A transient fetch error, or a later replacement that keeps the same fileUrl, leaves the icon view in place for the rest of the session.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fbe9297. Configure here.

Move `pdf-preview/pdf-search-hits.ts` back to the path the file had
before the search bar moved out, so the diff shows the file's changes
rather than a deletion and a new file.
The rule now lives in `folio-single-prosemirror-rule.js`, and
`next.config.js` imports it.

The rule lists the packages it redirects in two arrays: those HASH's
editor also installs, and those only folio uses. It no longer derives
them from folio's `package.json` files with a regex. Node's
`findPackageJSON` replaces the hand-written walk up the `node_modules`
folders. The check that stops the build when folio and the frontend
share a copy now runs over the first array, so it also covers
`prosemirror-dropcursor`, which HASH installs through
`hash-isomorphic-utils`.

The rule's output is unchanged: the same 13 aliases and the same
`include` directories.
@github-actions

Copy link
Copy Markdown
Contributor

Benchmark results

hash-graph-benches – Integrations

policy_resolution_large

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2002 $$28.7 \mathrm{ms} \pm 254 \mathrm{μs}\left({\color{gray}2.48 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.66 \mathrm{ms} \pm 38.9 \mathrm{μs}\left({\color{gray}1.81 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 1002 $$14.3 \mathrm{ms} \pm 136 \mathrm{μs}\left({\color{red}7.50 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 3314 $$46.6 \mathrm{ms} \pm 524 \mathrm{μs}\left({\color{gray}1.61 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$16.4 \mathrm{ms} \pm 218 \mathrm{μs}\left({\color{gray}3.07 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 1527 $$26.7 \mathrm{ms} \pm 256 \mathrm{μs}\left({\color{gray}3.46 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 2078 $$30.3 \mathrm{ms} \pm 304 \mathrm{μs}\left({\color{gray}2.90 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$4.11 \mathrm{ms} \pm 43.7 \mathrm{μs}\left({\color{red}8.92 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 1033 $$15.5 \mathrm{ms} \pm 167 \mathrm{μs}\left({\color{red}8.10 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_medium

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 102 $$3.92 \mathrm{ms} \pm 35.4 \mathrm{μs}\left({\color{gray}1.07 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.08 \mathrm{ms} \pm 23.7 \mathrm{μs}\left({\color{gray}-1.581 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 52 $$3.47 \mathrm{ms} \pm 33.3 \mathrm{μs}\left({\color{gray}0.589 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 269 $$5.42 \mathrm{ms} \pm 47.6 \mathrm{μs}\left({\color{gray}0.188 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.66 \mathrm{ms} \pm 23.0 \mathrm{μs}\left({\color{gray}-0.921 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 108 $$4.33 \mathrm{ms} \pm 36.9 \mathrm{μs}\left({\color{gray}0.204 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 133 $$4.76 \mathrm{ms} \pm 46.6 \mathrm{μs}\left({\color{gray}4.78 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.53 \mathrm{ms} \pm 24.5 \mathrm{μs}\left({\color{gray}0.380 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 63 $$4.38 \mathrm{ms} \pm 42.9 \mathrm{μs}\left({\color{gray}4.28 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_none

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2 $$2.81 \mathrm{ms} \pm 25.0 \mathrm{μs}\left({\color{gray}-0.210 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.82 \mathrm{ms} \pm 26.9 \mathrm{μs}\left({\color{gray}2.67 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 2 $$2.91 \mathrm{ms} \pm 27.3 \mathrm{μs}\left({\color{gray}1.56 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 8 $$3.15 \mathrm{ms} \pm 26.1 \mathrm{μs}\left({\color{gray}-0.641 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$2.95 \mathrm{ms} \pm 20.7 \mathrm{μs}\left({\color{gray}0.701 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 3 $$3.28 \mathrm{ms} \pm 25.1 \mathrm{μs}\left({\color{gray}-1.509 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_small

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 52 $$3.29 \mathrm{ms} \pm 26.5 \mathrm{μs}\left({\color{gray}4.68 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.90 \mathrm{ms} \pm 23.2 \mathrm{μs}\left({\color{gray}2.36 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 26 $$3.18 \mathrm{ms} \pm 37.3 \mathrm{μs}\left({\color{red}5.95 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 94 $$3.78 \mathrm{ms} \pm 35.2 \mathrm{μs}\left({\color{gray}3.11 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.23 \mathrm{ms} \pm 31.1 \mathrm{μs}\left({\color{gray}1.61 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 27 $$3.60 \mathrm{ms} \pm 29.3 \mathrm{μs}\left({\color{red}5.14 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 66 $$3.60 \mathrm{ms} \pm 36.5 \mathrm{μs}\left({\color{gray}0.165 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.20 \mathrm{ms} \pm 25.2 \mathrm{μs}\left({\color{gray}0.332 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 29 $$3.53 \mathrm{ms} \pm 34.9 \mathrm{μs}\left({\color{gray}2.01 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_complete

Function Value Mean Flame graphs
entity_by_id;one_depth 1 entities $$35.9 \mathrm{ms} \pm 299 \mathrm{μs}\left({\color{red}10.5 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 10 entities $$75.3 \mathrm{ms} \pm 521 \mathrm{μs}\left({\color{gray}1.68 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 25 entities $$38.4 \mathrm{ms} \pm 279 \mathrm{μs}\left({\color{gray}2.60 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 5 entities $$43.3 \mathrm{ms} \pm 358 \mathrm{μs}\left({\color{red}7.18 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 50 entities $$44.7 \mathrm{ms} \pm 303 \mathrm{μs}\left({\color{gray}3.32 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 1 entities $$35.3 \mathrm{ms} \pm 264 \mathrm{μs}\left({\color{gray}2.42 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 10 entities $$435 \mathrm{ms} \pm 1.31 \mathrm{ms}\left({\color{gray}1.63 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 25 entities $$90.8 \mathrm{ms} \pm 714 \mathrm{μs}\left({\color{gray}-4.020 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 5 entities $$83.8 \mathrm{ms} \pm 724 \mathrm{μs}\left({\color{gray}3.62 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 50 entities $$318 \mathrm{ms} \pm 1.17 \mathrm{ms}\left({\color{red}14.4 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 1 entities $$10.7 \mathrm{ms} \pm 71.9 \mathrm{μs}\left({\color{gray}2.11 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 10 entities $$11.0 \mathrm{ms} \pm 78.6 \mathrm{μs}\left({\color{gray}3.42 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 25 entities $$11.3 \mathrm{ms} \pm 104 \mathrm{μs}\left({\color{gray}4.62 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 5 entities $$11.2 \mathrm{ms} \pm 115 \mathrm{μs}\left({\color{red}6.11 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 50 entities $$11.2 \mathrm{ms} \pm 109 \mathrm{μs}\left({\color{gray}4.45 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_linkless

Function Value Mean Flame graphs
entity_by_id 1 entities $$10.9 \mathrm{ms} \pm 84.8 \mathrm{μs}\left({\color{gray}3.79 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10 entities $$11.0 \mathrm{ms} \pm 102 \mathrm{μs}\left({\color{gray}4.38 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 100 entities $$11.2 \mathrm{ms} \pm 87.0 \mathrm{μs}\left({\color{red}6.94 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 1000 entities $$11.1 \mathrm{ms} \pm 83.2 \mathrm{μs}\left({\color{red}6.62 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10000 entities $$11.5 \mathrm{ms} \pm 97.2 \mathrm{μs}\left({\color{red}6.98 \mathrm{\%}}\right) $$ Flame Graph

representative_read_entity

Function Value Mean Flame graphs
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/block/v/1 $$11.4 \mathrm{ms} \pm 95.9 \mathrm{μs}\left({\color{gray}4.41 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1 $$11.7 \mathrm{ms} \pm 84.6 \mathrm{μs}\left({\color{red}8.04 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1 $$11.4 \mathrm{ms} \pm 91.6 \mathrm{μs}\left({\color{red}5.25 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1 $$11.2 \mathrm{ms} \pm 78.9 \mathrm{μs}\left({\color{gray}2.10 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2 $$11.6 \mathrm{ms} \pm 108 \mathrm{μs}\left({\color{red}6.01 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1 $$11.5 \mathrm{ms} \pm 113 \mathrm{μs}\left({\color{gray}4.11 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1 $$11.5 \mathrm{ms} \pm 102 \mathrm{μs}\left({\color{red}6.01 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1 $$11.4 \mathrm{ms} \pm 90.7 \mathrm{μs}\left({\color{gray}4.31 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1 $$11.4 \mathrm{ms} \pm 87.4 \mathrm{μs}\left({\color{gray}4.02 \mathrm{\%}}\right) $$ Flame Graph

representative_read_entity_type

Function Value Mean Flame graphs
get_entity_type_by_id Account ID: bf5a9ef5-dc3b-43cf-a291-6210c0321eba $$8.27 \mathrm{ms} \pm 64.9 \mathrm{μs}\left({\color{gray}2.38 \mathrm{\%}}\right) $$ Flame Graph

representative_read_multiple_entities

Function Value Mean Flame graphs
entity_by_property traversal_paths=0 0 $$59.4 \mathrm{ms} \pm 573 \mathrm{μs}\left({\color{gray}3.49 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$112 \mathrm{ms} \pm 884 \mathrm{μs}\left({\color{gray}2.63 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$65.6 \mathrm{ms} \pm 582 \mathrm{μs}\left({\color{gray}-0.465 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$76.8 \mathrm{ms} \pm 650 \mathrm{μs}\left({\color{red}5.52 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$83.9 \mathrm{ms} \pm 674 \mathrm{μs}\left({\color{gray}2.65 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$91.8 \mathrm{ms} \pm 865 \mathrm{μs}\left({\color{gray}4.01 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=0 0 $$47.7 \mathrm{ms} \pm 309 \mathrm{μs}\left({\color{gray}1.07 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$76.2 \mathrm{ms} \pm 556 \mathrm{μs}\left({\color{gray}3.61 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$53.9 \mathrm{ms} \pm 433 \mathrm{μs}\left({\color{gray}1.28 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$63.6 \mathrm{ms} \pm 586 \mathrm{μs}\left({\color{gray}2.40 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$66.1 \mathrm{ms} \pm 521 \mathrm{μs}\left({\color{gray}2.63 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$65.9 \mathrm{ms} \pm 771 \mathrm{μs}\left({\color{gray}4.17 \mathrm{\%}}\right) $$

scenarios

Function Value Mean Flame graphs
full_test query-limited $$116 \mathrm{ms} \pm 850 \mathrm{μs}\left({\color{gray}1.42 \mathrm{\%}}\right) $$ Flame Graph
full_test query-unlimited $$127 \mathrm{ms} \pm 852 \mathrm{μs}\left({\color{gray}0.257 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-limited $$17.7 \mathrm{ms} \pm 147 \mathrm{μs}\left({\color{lightgreen}-5.735 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-unlimited $$512 \mathrm{ms} \pm 1.70 \mathrm{ms}\left({\color{lightgreen}-6.081 \mathrm{\%}}\right) $$ Flame Graph

This branch was successfully deployed

3 active (1 outdated) and 2 inactive deployments
Preview – hash — 103d35f1 Deployed Oct 13, 2026 by vercel[bot]
pull-request — 103d35f1 Deployed Oct 13, 2026 by 0xRezaa via Sourcemaps (@apps/hash-integration-worker) #39714
Preview – petrinaut-docs — 103d35f1 Deployed Oct 13, 2026 by vercel[bot]
Preview – petrinaut — 103d35f1 Deployed Oct 13, 2026 by vercel[bot]
Preview – hashdotdesign-tokens — 20b203c6 Deployed Oct 13, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps > hash* Affects HASH (a `hash-*` app) area/apps area/deps Relates to third-party dependencies (area) area/tests > playwright New or updated Playwright tests area/tests New or updated tests type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

3 participants