Commit History
-
feat(storage): first-class data - no silent failures in the persistence pipeline
The "Saved" label in the top bar used to be a comforting lie: several failure modes (createRepo 403 on a missing manage-repos scope, writeFileSync on a readonly FS, uploadFile mid-debounce network blip) all collapsed into a console.error that the editor never surfaced. A user could type for an hour, see "Saved" the whole time, lose their tab, and discover only after the container rebuilt that nothing had ever made it to the dataset. Make every link in the chain observable, and make the worst signal always win in the UI. Backend ------- - In-memory `StorageStatus` tracker in hf-storage.ts: - `datasetReady` (createRepo succeeded or returned 409) - `lastLocalSaveAt` / `lastCloudPushAt` epoch ms - `pendingPush` (a debounce timer is armed) - `lastError {stage, message, statusCode, at, docName}` where `stage` is one of `dataset-create | local-save | cloud-push` Every write path records success; every error path records the failure. Success on one stage clears the error for …7a42df5 -
feat(storage): private dataset by default + /d/* reverse proxy
The dataset that backs persistence was being created `private: false` on every first write. For org-owned Spaces with strict data policies (notably `HuggingFaceBio/carbon-tokenization`) this could either silently fail (org blocks public dataset creation) or be flat-out wrong (working drafts of an internal research paper shouldn't be sitting on a public dataset). Flip the default to `private: true`. The complication this creates is that published articles' `<img>` tags, og:image and PDF download all pointed at `huggingface.co/datasets/.../resolve/...` URLs that would now 401 for anonymous viewers. Solve that with a small reverse-proxy route on the editor itself. - New `GET /d/:path*` handler streams from `huggingface.co/datasets/${id}/resolve/main/${path}` to the caller, attaching the right HF token along the way: 1. cookie token from the current request (most viewers are signed in on private Spaces anyway) 2. last-known cached user token (any signed-in request warm…c362999
-
revert(auth): drop OAuth popup flow, restore plain new-tab login
The popup + window.top.location workaround (bdbc54d, e94e499) turned out to break the very thing it was meant to improve. In practice modern browsers blocked the cookie on iframe reload because of third-party cookie policies, and the fallback that navigated window.top dumped the visitor out of the HF embed altogether - net result: extra moving parts AND a worse UX than the original target="_blank" tab. Revert to the original behavior: the "Sign in" button is a plain <a href="/oauth/authorize" target="_blank"> that opens the auth flow in a new tab. The new tab lands on *.hf.space/editor first-party once auth completes; the visitor uses that tab. Specifically this revert removes: - The pendingStates entry shape upgrade (popup boolean). Map is back to a flat Map<string, number> timestamp store. - The ?popup=1 query in handleOAuthAuthorize. - The popup branch in handleOAuthCallback that served a self-closing landing page. - The renderPopupClosePage() helper. - The inlin…9fba033 -
fix(auth): drop HF iframe on popup close so cookie is first-party
The popup OAuth flow shipped in bdbc54d had a latent third-party cookie problem that only surfaces on modern browsers: - The popup runs as its own top-level browsing context with top-level = *.hf.space. The Set-Cookie at the end of the OAuth round-trip therefore lands as a first-party cookie there. So far so good. - The parent iframe's top-level is huggingface.co (HF embeds every Space inside huggingface.co/spaces/<owner>/<name>). Reloading the iframe issues a request to *.hf.space FROM that top-level - i.e. a third-party request. - Chrome 124+'s ongoing TPC phase-out, Safari ITP, and Firefox strict mode all refuse to attach the *.hf.space cookie to a third-party request now. The reload therefore arrives with no auth cookie and the login page reappears, looking like "the popup did nothing". Fix: when the popup closes, navigate window.top (not window) to *.hf.space/editor. That's a cross-origin top-level navigation, which browsers ALLOW (o…e94e499 -
feat(auth): OAuth popup window instead of full-tab redirect
When the visitor is inside the HF Space iframe (i.e. browsing huggingface.co/spaces/<owner>/<name> rather than the bare *.hf.space backend URL), clicking "Sign in" used to open a brand new full-screen tab. That kicks the user out of the Space's visual context, drops them on the *.hf.space origin until they manually navigate back, and requires a hard refresh of the iframe to pick up the new cookie. Replace with a centered popup window that closes itself once the auth round-trip completes: Frontend (login page, served by renderLoginPage): - Inline script intercepts the button click ONLY when we're in an iframe (window.self !== window.top). Same-tab direct visits keep the classic href behavior - that path was already optimal. - Appends ?popup=1 to /oauth/authorize so the backend knows to render a self-closing landing page on return. - Polls w.closed every 400ms; on close, reloads the iframe to pick up the freshly-set cookie. - If window.open returns null (popup blocke…bdbc54d -
chore(ws): verbose auth logs to triage Disconnected reports
Add structured logging on both ends of the WS handshake so a "Disconnected" label in the editor top bar can be pinpointed without guessing: - Upgrade handler logs the cookie header size and whether extractToken found our session cookie. This catches the case where the browser doesn't ship cookies on the WS upgrade (some HF Space gating + private/org setups strip them on upgrade even when plain HTTP requests send them fine). - onAuthenticate logs which source supplied the token (the client subprotocol or the server-side cookie fallback), the resolved user, the configured Space owner, and any accessIssue surfaced by resolveUser. Errors thrown to Hocuspocus now also include the username + accessIssue so the reason is visible in the rejected-connection log line, not just a generic "Unauthorized". Logs intentionally avoid printing the token itself - they only expose its length and origin, matching the existing privacy posture of the auth pipeline. Co-authored-by: Curs…038fa8d -
fix(auth): use roleInOrg from whoami, drop broken members API
The Space owned by an org never let any signed-in user through to the editor, even with full org write access. Root cause: we were reading the role from \`https://huggingface.co/api/organizations/{name}/members\`, but that endpoint returns members as plain user records (\`{ user, fullname, avatarUrl, isPro, type, _id }\`) with no \`role\` field. The check \`member.role === "write" || "admin"\` was therefore always false and every org-member visitor ended up on the login screen. Switch to the \`roleInOrg\` field that whoami already returns in \`info.orgs[i]\`. It carries one of \`admin | write | contributor | read\`, exactly what we need, and we already paid for that round-trip via \`whoAmI()\`. The org-members API call is dropped entirely. Also pick up the \`resourceGroups\` field from the same payload: HF lets \`contributor\` users get \`write\`/\`admin\` on specific groups, and the Space may live in one of those groups. Treat "contributor at org level + any write/admin group" as wr…7652473
-
feat(publisher): generate llms.txt Markdown twin for LLM agents
External LLMs/agents (Claude, Perplexity, ...) struggle with the heavy published HTML: theme bootstrap, KaTeX/Mermaid runtimes, ~7 inlined CSS files, TOC chrome, and D3 charts wrapped in <iframe srcdoc>. The static research-article-template already ships an `llms.txt` (Astro build plugin); the editor's publisher now does the same, walking the TipTap-JSON we already extract for the HTML/PDF pipeline. Pipeline addition (publisher/index.ts): - After writing index.html, render the same JSON to Markdown via the new markdown-renderer.ts and write `published/<doc>/llms.txt` - Re-render with the public HF meta and include it in the dataset upload commit (hf-storage.ts adds llms.txt to the file list) Routing (create-app.ts): - GET /llms.txt serves the published Markdown twin with Content-Type: text/markdown; charset=utf-8 (404 before publish) - GET /robots.txt advertises it via the `LLMs-Txt:` header, mirroring the upstream template Conventions in the renderer mirror the upstream Ast…3de227d
-
refactor(login-page): neutral title, outlined CTA, drop wordmark
- Title back to default text color (dropping the primary tint) - Switch the CTA from a filled pill to an outlined pill: 1.5px primary border, transparent background, color-mix tint on hover/active - Remove the "Research Article Template Editor" wordmark for a cleaner silhouette Made-with: Cursor0332cd4 -
feat(login-page): full HF logo, primary title, pill CTA, refined layout
- Inline the complete official Hugging Face logo SVG (face + both hugging hands), fixing the previously truncated mark in the button - Move the title to primary color and bump it to a fluid clamp size with Apple-grade letter-spacing - Replace the outlined CTA with a pill-shaped primary button: vertical gradient fill, layered inset highlights, soft elevation, 1px lift on hover with deeper shadow, slightly tighter letter-spacing - Drop the "after signing in, refresh this page" hint since the OAuth flow redirects automatically - Add a soft radial primary tint on the page background and a discreet wordmark below the CTA for brand presence - Use the SF / Source Sans Pro stack with antialiasing tuned for both light and dark modes - Simplify renderLoginPage: hint slot removed, callers now only pass title and description Made-with: Cursor7f90538 -
feat(login-page): align with app design tokens, bigger outlined HF button
- Extract a single renderLoginPage() helper used by both the editor login gate and the published-article fallback (no more duplicated inline HTML) - Use the app's primary color (OKLCH) and Source Sans Pro font, with proper light/dark via prefers-color-scheme so the page no longer looks like an unrelated dark stub - Replace the small filled button with a bigger outlined CTA (14×22 px padding, 1.5px primary border, color-mix hover) - Inline the official Hugging Face brand logo SVG (no external network dependency, no emoji fallback) - Escape user-controlled hint substitutions through a dedicated escapeHtml() Made-with: Cursord8c3c68
-
refactor(backend): modular server split with new routes, persistence and agent layer
- Introduce create-app factory to compose routes, middleware and Hocuspocus in isolation - Extract route modules (auth, chat, publish) and move storage logic into persistence.ts - Harden auth + HF storage integration (user token fallback, secure cookie in production) - Add embed studio AI pipeline (embed-chat, embed-tools, system prompt) and shared stream handler - Cover the new surface with api-routes, persistence and agent-chat test suites - Ship .env.example and .gitignore so new contributors can boot without guesswork Made-with: Cursorf6678ab