Spaces:
Sleeping
Sleeping
Commit History
-
Close four holes in the share link, from review
1. A signed-in visitor could not use a link. authenticate() only consulted the share cookie when nobody was signed in, so a session made `user` truthy and the ACL check then refused them — while REST let the same request through. The page loaded and never synced. The grant now applies whenever the visitor lacks access of their own, signed in or not, and boot says which case it is so the client shows the read-only view to a signed-in stranger too. 2. The token stopped living in the URL. /p/<token> now sets the cookie and redirects to /d/<id>: a rendered page left the credential in the address bar, in history, and — since documents carry external images and links — in the Referer header sent to whatever host those point at. Referrer-Policy: no-referrer is set for the whole app as well. 3. Revoking reaches sockets that are already open. A viewer authenticated before the token changed stayed attached and kept receiving edits, so "revoked" meant "no new vi…830cb4d -
Hide the hover-comment button from link visitors too
It arrives with #15 (comment beside a paragraph, from a hover in the right margin). A visitor holding a read-only link must not be offered a composer whose write the socket then drops — the rule is harmless until that PR lands. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>a6671ed -
Anyone with the link: read a project without an account
A second way to share, next to inviting people by username: one long unguessable URL that opens the project read-only, for someone who has no Hugging Face account and should not need one. The token (24 random bytes) is exchanged for a cookie the moment the visitor lands on /p/<token>, so nothing downstream has to carry it: the REST snapshot, the websocket handshake and the <img> requests are all covered by the same grant. Lookup is constant-time, and an unknown or revoked token is a 404 that does not confirm the project exists. Read-only is enforced where it counts. The connection is marked readOnly in onAuthenticate, so hocuspocus drops any update or awareness message the client sends: a visitor who types sees their own keystrokes and nothing else does. REST keeps requireUser everywhere except GET of the project and its page tree, so a link cannot suggest, comment, add a page, re-share or delete. The non-editable editor and the hidden buttons are a courtesy on top of that, not the m…3a73292
-
Agents can open their own comments, and suggestions preview on subpages
Two halves of the same problem: giving feedback in small pieces. Agents could only reply into a thread a human had already opened — there was no way to leave several small remarks across a long page, so everything arrived as one long reply. POST /docs/:id/threads opens a thread: anchored with block_index (or an anchor pair from GET /docs/:id) to mark the text it is about, or with no anchor at all for a comment about the page as a whole. One call per point; each is resolved on its own. The agent prompt now says to prefer that over one wall of text, and to keep passing mention_id to close the request. A page-level thread needed no client work — cards come from the threads map and layoutMargin already stacks a card with no range after the anchored ones — but it now says "On this page", so it does not read as a comment whose highlight went missing. The subpage half: anchors are relative positions into a page's OWN fragment, and three places reached for 'default', which is home's. On any…94f8eb1
-
Keep feedback actions visible and mark comment ranges
Agent Managera54b6e7
-
Keep traceability after source comments close
Agent Manager7cdc17b -
Make agent feedback standalone and traceable
Agent Manager26dfc7b
-
Merge pull request #18 from huggingface/agent-privacy
Leandro von WerraUnverified signatureAn agent you register is not shared with your collaboratorsb622b57
-
Merge pull request #17 from thomwolf/agent/mobile-reflow
Thomas WolfUnverified signatureA phone could not read the document, only pan across it5762a8e -
Merge pull request #16 from thomwolf/sidebar-search
Thomas WolfUnverified signatureThe sidebar box searches page text, not just page titlesf9e13f4 -
Merge pull request #15 from thomwolf/agent/hover-position-comment
Thomas WolfUnverified signatureComment beside a paragraph, from a hover in the right margin5ad864d -
Guard the invariant the two search walks rest on, and the review nits
The mutation testing in review found one real gap: nothing failed when docHitRanges stopped counting a character per non-text inline node, because no fixture paragraph had an atom before a hit. The new probe paragraph puts a mark and two formulas ahead of one, and asserts the contract directly — an atom is one character no query can match, so `wom`+formula+`bat` is not a hit. That kills the mutation in both walks (as a spurious mark in the document, as a count the sidebar never made). The second escaping mutation, docPos -> pos + 1 + off, is arithmetically identical given that invariant and cannot be killed while the invariant holds; break both and marks slide left by one per atom. Documented at the code rather than chased with a test. Also: lowercase each block once at index time instead of once per repaint; never let the read-only index path create a Yjs type (_structure and ghost pages return first); one history entry per hit click, on the open page as much as across pages; an out…064739c -
Tell a merged-away paragraph from a deleted one
Claude +1: Claude Opus 5 (1M context)Backspace at the start of an anchored paragraph joins it into the one above. The block boundary goes, so the anchor lands outside any text block exactly as it does for a real deletion — but every word survives one line up, and the card struck them through and said the paragraph had been deleted while pointing straight at them. orphaned no longer means "the anchor is not inside a block". When the position gives up, the text is asked instead: a merge leaves the remembered words in a block touching that boundary, a deletion does not. Comparing text would be the wrong tool for judging a live block, and is the right one here because it only runs once the position has already failed. Also: drop the hover button on scroll, since it travels with the text and ends up beside a paragraph the pointer has left; document that a table row anchors to its leftmost cell; and correct the comment claiming a mid-page delete reuses the surviving element — measured, it orphans like any other. Tests: the m…e5d08da -
A dragged table put the sideways pan back, and the tests could not see it
Review found the ≤840 block let one class of content overflow exactly the way this PR exists to prevent, and that the new tests were structurally unable to catch it. Both reproduced before changing anything. The comment at the table rules claimed "No min-width, deliberately". That is true of the stylesheet and false of the DOM: prosemirror-tables records a column drag as inline geometry — style="min-width:561px" on the table, style="width: 513px" on a <col> — and inline styles are stored in the document, so a drag on a desktop follows the reader onto their phone. Measured at 390 on the branch tip: a 562px table inside a 354px measure, with 190px of horizontal scroll on #editor-col (220px at 360). The reader pans across the row. Both declarations are needed. Measured, same repro: col { width: auto !important } 561px table, 189px scroll table { min-width: 0 !important } 562px table, 190px scroll both 354px table, 0px scroll both, !…a404034 -
Give the toolbar a row of its own when it stops fitting
Review of #14 found the pair pushed #header-tools from 721px to 818px and past its track on a 1280px window: the readout truncated to "15p", #text-larger gone, and #image-btn — which fits on main at that size — gone with it. The scrollbar is suppressed by design, so nothing said so. The reviewer suggested a breakpoint at ~1300px. Measuring says a breakpoint cannot do it: #doc-title is capped at 260px but a short one is ~66px, so the same window is ~180px better or worse off depending on the title. With a short title the branch stopped fitting at 1340px; with a full-length one at 1520px. main has the same bug and only wore it more quietly — it clipped 48px at 1200px, and 2px at 1440px with a long title. So it is measured, next to fitLayout() and asking the same question of the header that fitLayout() asks of the columns: if the toolbar does not fit beside the title and the actions, it takes a row of its own — the layout the 980px breakpoint already uses, now for its own reason. Nothin…f64215e -
Add a private default agent for each user
Agent Manager314a59f -
An agent you register is not shared with your collaborators
Registering an agent stored no visibility flag, and every read treated a missing flag as shared: agentPresence() shared: info.shared !== false -> undefined means shared mention routing agents[h]?.shared !== false || ... -> undefined means shared So a handle was exposed the moment it was created: every collaborator on every doc you shared could see it in the agent list and @mention it into work. The toggle in the UI worked, but it defended a door that was already open — and the existing test set shared:false explicitly, so it never saw the default. Absent now means unshared, in both reads, and registration writes the flag down instead of leaving it to be inferred. Re-registering your own handle keeps the choice you made before. This matters because an agent is not a document permission: it runs on your machine, with your credentials, and does work in your name. Sharing it is a deliberate act — one click on the toggle that already exists. Behaviour change: existing hand…abaa17c -
A phone could not read the document, only pan across it
The sheet was a fixed-width page at every window size: 8.5in of paper with horizontal scroll below about 900px, and zoom as the only way to fit it. That works on a desktop, where the page is the point. On a handset it means the reader pans left and right to follow a line of text. Reflow the sheet into the screen, in two stages, so the change is never louder than the window forces: * ≤840px changes geometry only. The sheet goes fluid but the text column is still held at the 624px measure the page itself gives, so a 768px tablet lands on the same measure, the same 11pt Arial, and the same line breaks as a desktop — with the horizontal scroll simply gone. Crossing 840 does not re-break a single line. * ≤700px is where a handset cannot hold 624px at any margin, so the type gives too: 16px base, 18px minimum gutters, 354px of measure and ~42 characters a line at 390px. Zoom changes meaning across that line and now has two floors. Where the page is a page, 0.4 …29c066e -
The sidebar box searches page text, not just page titles
Filtering the tree by title only finds a page if you already remember what it is called. Every page of a project is a named fragment of the one Yjs document the tab already syncs, so the text to search is in memory before the first keystroke — no endpoint, no per-page fetch, and no permission logic to re-derive, since the socket handshake already decided access. The index is built from the live CRDT, so it follows a collaborator's typing. A match now shows its occurrences as snippets under the page, each labelled with the heading it sits under, and clicking one opens that page scrolled to that occurrence and no other. Hits in the open document are marked in amber — not the accent green, which on this page always means somebody said something about the text. The query lives in the URL (?q=&n=), so a search is a link you can send someone. Titles and slugs still match from the first character; searching text waits for a second one, or one letter highlights half the prose. Co-Authored-B…8c82c03 -
Comment beside a paragraph, from a hover in the right margin
Claude +1: Claude Opus 5 (1M context)Hover the right third of the page and a small comment button appears out in the page's own right margin, level with the paragraph you are next to. Click it and you get a comment anchored to that place, with no selected span. The anchor is one Yjs relative position taken inside that paragraph — the same machinery the existing span comments use, written into both anchor fields so every path that already resolves a thread's anchors keeps working and simply sees an empty range. The card shows the paragraph's opening words as a locator instead of a quote, because there is nothing quoted; agents get anchor_kind + anchor_context so a null excerpt is not mistaken for deleted text. Deleting the paragraph never closes the thread. The zone is hover-only and invisible: no gutter, nothing while a selection or a composer owns the flow, and nothing at all on touch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>f9ca812 -
A reader text size next to zoom: the knob that does reflow
Zoom scales the sheet and the type by one factor, so the words-per-line never moves — deliberately, because a page that reflowed would not be a zoom. This adds the other control a reader actually wants: --doc-text-scale multiplies the type and nothing else, so the page keeps the width it has on screen and the lines re-break. A Kindle's A/A, next to the magnifiers. The stored value is a factor rather than a size, so it rides on top of whatever the document style asks for: one step up in Docs stays one step up after switching to Reading, and each style still decides what 100% means for it (14.6667px and 16px). An absolute size would have had to either lose the reader's choice on a style switch or overrule the style's own typography. Persisted as doc-text-scale, alongside doc-zoom and cw-doc-style. Left out of the print path on purpose: it is one reader's comfort setting in one browser, and a PDF is handed to other people. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.c…f6cf611
-
Four fixes: agent subpages, the pending selection, overlapping marks, the star
AN AGENT'S SUBPAGE LANDED AT THE TOP LEVEL. Placement worked only for an exact ref — a page's slug, or a group's quoted path array. But an agent works from what it can see, and what it sees is a TITLE: sending "Research Notes" for the page research-notes, or 'Getting started' for the group '"Getting started"', was a 400, so a proposal made without placement went to the top level. Parents now resolve from a slug, a page title (any case), a group label with or without its quotes, or a one-element array holding any of those; parent_slug/parent_page/under work as field names. Exact refs still win before any guessing. The response and GET .../page-suggestions/:id both echo the resolved parent and index, so a caller can see its intent was understood, and an unknown parent is refused with the real page list attached. THE TEXT YOU WERE COMMENTING ON WENT UNMARKED. Focusing the composer moves focus out of the editor, which drops the browser's own selection highlight — so the words you had pick…63bc919 -
Download: this page or the project, as markdown or PDF
A Download button in the header opens two choices — how much (this page / whole project) and in what shape (markdown / PDF). MARKDOWN is a zip built server-side: one .md per page in sidebar order, plus an assets/ folder holding the images those pages use, with every /files/<hash> reference rewritten to its bundled copy. That rewrite is the point — a /files URL only resolves for someone signed in to this Space, so without it an exported document renders as broken images anywhere else. Project scope also carries structure.yaml, since the yaml IS the project's shape (order, nesting, group headers) and without it an unzipped project is a flat pile of files. An image whose bytes have gone missing is listed in assets/MISSING.txt rather than failing the whole export. The zip is written by server/zip.js, ~100 lines over node's zlib, rather than by adding a dependency: the Space builds with `npm ci`, which hard-fails on any package.json/package-lock disagreement, and this is not worth that ri…3b2fb14 -
Proposed pages live in the tree, where they would land
Three things, all about the page tree. AGENTS COULD NOT PROPOSE A PLACE. page-suggestions took a title and a body and nothing else, so an accepted page was appended at the top level wherever the agent meant it to go — and there was no way to say otherwise. Proposals now take the same {parent, index} refs /structure/move already uses: a page parent is its slug, a group parent is its path array, no parent means top level. The placement rides on the proposal, so accepting files the page there via moveNodesInYaml. An unknown parent is a 400 (it used to be accepted and quietly flattened), and the agent instructions in api.js document the field with examples. PROPOSALS SAT IN A LIST UNDERNEATH EVERYTHING. They were rendered after the tree and the unfiled section, under a "proposed" heading, which hid the one thing a reviewer wants to know: where is this page going? They are now grafted into the tree at their proposed position and rendered as ordinary rows — dashed, badged, still opening th…aeb6b2b -
Make groups draggable again
The interactive-control drag guard swallowed pointerdowns on group labels, which are <button>s — pages kept dragging because their labels are anchors. Controls can now opt back in with data-td-drag, and the group label (the group's whole hit area) does. The e2e now drags groups directly — empty, with subtree, in and out of a page. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>66103fc
-
Left carets, home star, project subtitles, empty-group rename fix
Fold carets move to the left of the label (aligned spacer for leaf rows); the hover trash overlays the row end so long titles run to the edge. A muted star marks the home page. Renaming a group with no children no longer loses its input — every group-label single-click consequence now defers behind the same 280ms timer the double-click cancels. The home page's first paragraph becomes the project's subtitle (derived on store, persisted on the registry) and previews under each title in the project dropdown. Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>3f3e761 -
Dialog-free UI, project-name header, rl-wiki sidebar polish
The Space runs in an iframe where Chrome suppresses native dialogs, so confirm()/alert()/prompt() silently did nothing — deletes looked broken. All 33 call sites are gone: sidebar deletes/dissolves are now arm-to-confirm buttons with toasts for errors; everything else goes through new in-app uiConfirm/uiAlert/uiPrompt/uiToast helpers (common.js, keyboard-complete, dark-mode aware). The browser tests now fail on any native dialog. The top-left selector always shows the project name (tab becomes 'page · project'), server-rendered too, live while renaming home. Sidebar: exact rl-wiki type scale and row treatments, search replaces the 'Pages' heading, trash sits at the far right of rows, and the bar is a seamless full-height surface — page background, no right border, reaching the writing sheet. treedrag no longer arms drags from buttons/inputs, so control clicks can't be swallowed. Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authore…d3283b8 -
treedrag: never start a drag from interactive controls
A pointerdown on a button, input, select, textarea, or contenteditable inside a row armed a drag, so a 4px wiggle while clicking a row's trash or caret swallowed the click. Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>c91ab4d -
rl-wiki-style sidebar: restyle, search filter, multi-select, inline renames
Full-height sticky sidebar with a title filter on top (Cmd/Ctrl-K, Escape clears; drag pauses while filtering). rl-wiki look: big right-side carets, vertical bars under branches, accent-tinted current row distinct from gray multi-selected rows. File/folder icon buttons create an untitled page (focus lands in its empty H1 once synced) or a group that opens straight into inline rename — no prompt() modals anywhere. Double-click renames pages and groups in place; shift/cmd-click builds a selection that drags as one block. The Structure link row is gone (the page keeps its URL). Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>8a9d88a -
treedrag: multi-select block drags
A drag starting on a selected row (data-selected) carries every selected root: union subtree dimming and drop rejection, DOM-order keys, a count badge on the ghost, and index math that subtracts all moved same-parent roots. onMove now always reports {keys, parentKey, index}. Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>1783a28 -
Sidebar groups, collapse carets, and drag-and-drop
Group headers render as small-caps section rows (click to fold, double-click to rename, hover trash to dissolve — children are promoted, never deleted). Carets fold any branch; the state is per-user in localStorage and the current page's ancestors stay open. Pages and groups reorder and re-nest by pointer drag; drops become structure/move calls so the server serializes concurrent edits. Unfiled pages are draggable into the tree to file them. Includes a manual browser test (test/e2e-dnd.mjs) driving real pointer drags against the built bundle. Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>2b806f6 -
Add a generic pointer-based tree drag module
Self-contained: no app imports, injects its own styles. Rows opt in via data-td-* attributes; drops report {key, parentKey, index} with the index counted after the dragged node leaves its old spot. Handles touch long-press, subtree protection, ancestor outdent on the last sibling, hover-to-expand, auto-scroll, and Escape cancel. Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>e099afd -
Merge pull request #6 from thomwolf/agent/adaptive-page-sidebar
Leandro von WerraUnverified signatureMake the page sidebar fill available space0b3e633 -
Remove the temporary layout tuner
5d6c651 -
Add a temporary layout sizing tuner
1596003 -
Keep comment opening from moving the document
11af3e7 -
Let comments overlap the page margin
319c03c -
Make the page sidebar space-aware
46a2576 -
Unify project pages in one Yjs document
9b49194 -
Prevent duplicate and stale suggestions
a5757a3 -
A link you can click, and a page body that is never dropped
Two reports, both about a thing that looked like it worked. CLICKING A CROSS-REFERENCE DID NOTHING. In an editor a plain click has to place the caret — you must be able to edit the words a link is made of — so following a link needed ctrl/cmd+click, and nothing on screen said so. There was no bubble on click either (#link-pop is the toolbar's URL input and needs a selection), so a plain click produced no navigation and no feedback at all. A plain click now raises a small bubble anchored to the link: the target named as a page title for internal links, an Open button, and Remove link. ctrl/cmd+click and tap-to-follow are unchanged. handleClick and the bubble now share one followLink(). A PAGE BODY SENT UNDER THE WRONG FIELD NAME VANISHED. page-suggestions read content_markdown and nothing else, so an agent that sent "markdown" got a 200, an empty proposal, and no way to know why (reported: "both pages arrived title-only"). Bodies are now read through pickMarkdown() — the canonical nam…fbcd415 -
Merge pull request #3 from thomwolf/agent/close-comment-ui
Leandro von WerraUnverified signatureCall finished comments closedfa3e297 -
Call finished comments closed
085e5b4 -
Hide the skeleton for an empty account
505a1e7 -
One document at every width: narrow windows scroll, never reflow (#11)
- One document at every width: narrow windows scroll, never reflow (1463c4e25851e413b7851146150848538dccdab1)675fbd2 -
Comments are closed, not ticked off — and the sheet answers the thumb (#8)
- Comments are closed, not ticked off — and the sheet answers the thumb (a3c1c4271a2cf4fa88ab255027a2288cf6314b9d)04b2acc -
The document is a page: fixed width, folding columns, real zoom (#6)
- The document is a page: fixed width, folding columns, real zoom (090088f4fada3864f46747fa9c6b85f38aba6b69)779f7df
-
Table columns resize by dragging a cell border (#1)
- Table columns resize by dragging a cell border (3156788cc1775b841956c2fd3e6d44ac1e5dddeb) Co-authored-by: Thomas Wolf <thomwolf@users.noreply.huggingface.co>7e842fd -
Hand comments to an agent that is actually listening (#2)
- Hand comments to an agent that is actually listening (6fc2217c23e03d83ce5b588af3b2a0f8ca1a145e) Co-authored-by: Thomas Wolf <thomwolf@users.noreply.huggingface.co>6246de6 -
Document style switch: Google Docs' Arial, or a serif reading setting (#3)
- Document style switch: Google Docs' Arial, or a serif reading setting (6a0e4d3ec49a41633b87521c9dfac82a33fa7b22) Co-authored-by: Thomas Wolf <thomwolf@users.noreply.huggingface.co>2460317