Skip to content

Latest commit

 

History

History
481 lines (262 loc) · 26.8 KB

File metadata and controls

481 lines (262 loc) · 26.8 KB

✨ docs.plus demo

docs.plus: a shared page with people writing and talking all over it

Every line below is a filter, not an anchor. Click one and the page folds to that section alone.

👋 Start here

docs.plus is a shared document where every heading has its own chatroom.

This page is docs.plus running, not a picture of it. Try the things it describes as you read them.

Open the chatroom on this heading from the chat button on its table of contents row — try it here.

This document is read-only. /new opens a blank document you can write in.

Three headings, three chatrooms, one page The chatroom for a passage sits beside the passage, not at the bottom of the page.

Filtered to this section? Show the whole document.

💬 A chatroom on every heading

Every heading carries its own chatroom. The talk about a passage sits beside the passage, not at the bottom of the page and not in another app.

A reply under a heading needs no quoted context. The passage is directly above it.

One chatroom per heading

Messages posted under a heading belong to that heading. They show up nowhere else in the document. There is no general channel they fall into.

Open the chatroom on this heading, read it, and scroll back through it — try it here.

A dated line separates the days. New messages, edits and reactions arrive without a reload.

A reply carries the message it answers: the author, the time, and a short run of it. Click that quoted block and the chatroom opens the message it came from — try it here.

Signed out, the composer opens the sign-in dialog instead of sending — needs an account.

A heading, the passage under it, and the chatroom below A reply quotes the message, not the page. The page is already above it.

Counts sit in the table of contents

In the table of contents, a heading with messages swaps the chat button's icon for a count.

Signed out, that number is the chatroom's total message count. It counts every message in the chatroom, not the ones that are new to you.

Signed in, the same badge becomes your own unread count. Scrolling through the chatroom clears it — needs an account.

Comments point at a passage

A comment carries the passage it is about: an excerpt for text, a thumbnail for a picture or a video, framed in that media's colour.

Click the quoted block on a comment — try it here. The document scrolls to that exact run of text, not to the top of the section.

When the passage can no longer be found, the jump lands on the heading instead.

A comment starts in the document body. It posts into the chatroom of the heading that holds it — needs a document you can edit.

Who is in the chatroom

On desktop, a stack of faces sits on the table of contents row of every heading whose chatroom someone has open. Hover a face for the name, and for a mark when that person is typing — try it here.

A heading row shows up to four faces, then a count of the rest.

The stack follows open chatrooms, not scroll position. On a quiet document it stays empty.

Signed out, you see other people and they do not see you. Your own face joins a row once you sign in — needs an account.

Table of contents rows carrying a chat count and a stack of faces A count means messages. A face means someone has that chatroom open.

🔓 Read-only locks the text, not the chatroom

The lock stops at the document body. Every chat control on this page still works. The chat button on each table of contents row opens its chatroom. The composer sends once you are signed in, and the message arrives for anyone who has that chatroom open.

The text is fixed. The talk is not.

The table of contents is the exception. Its drag grip and its Delete Section entry still respond on this page. Both write into the document — needs a document you can edit. The server refuses what they send, so do not press them here.

Open the chatroom on this heading and say what you would use a per-heading chatroom for — needs an account.

What a chatroom takes once you sign in

Everything under this heading needs an account. This heading has sub-headings, so its table of contents row carries a chevron. Fold the section from that chevron to put it away — try it here.

Reactions

Each emoji on a message is a chip, and reading a chip takes no account.

A chip carries a number only when more than one person used that emoji. A single reaction shows the emoji alone.

On desktop, the smiley in the hover menu opens the full picker. On mobile, a long press offers eleven one-tap emoji, and a + for the rest — needs an account.

A reaction chip is not a toggle. On desktop, clicking a chip you are part of takes your own reaction back off, and it never adds one — needs an account.

Reaction chips, the desktop picker, and the mobile quick row One tap answers, and the chatroom stays short.

Files and voice notes

A chatroom takes what the page takes: images, video, audio, PDF, text, CSV, Markdown, JSON, Word, Excel, PowerPoint and ZIP.

The caps are 10 MB per file, 10 files per message, and 3 uploads at a time. A voice note runs to 5 minutes and draws 24 bars from the microphone as it records — needs an account.

Two to ten pictures in one message lay out as a single tiled block. Click a tile for the full-screen gallery, and download from there — try it here.

An image can be sent as a spoiler, blurred until the reader taps it. Video and audio cannot — needs an account.

Attachment caps, a staged file, and a spoiler tile A chatroom takes what the page takes, inside a fixed set of caps.

Keep a message for later

A bookmark saves a message to your own list, and a second tap takes it back off. The message keeps a Saved for later tag in the chatroom — needs an account.

The list opens from the bookmark icon in the document toolbar, in three tabs: in progress, archive, and read. View on a row opens that heading's chatroom at the message — needs an account.

A saved message, and the bookmark list beside it The message keeps a tag. The list keeps the message.

Mentions

Type @, then pick a name, to tell that person about the message. @everyone tells the whole chatroom — needs an account.

The alert reads the plain message text, so the whole name has to match. @harvey does not reach harvey_marzban, and @everyone_team does not fire @everyone.

A mention in a message is coloured and tappable. Tap it for that person's profile card — try it here.

A mention that matches, and one that does not The whole name, or no alert.

Alerts, and how loud they are

A bell in the document header holds the list: mentions, replies, reactions, thread messages, channel events, direct messages, invitations and system alerts. The bell is hidden until you sign in.

View on a row opens that heading's chatroom at the message, and Mark all read clears the list — needs an account.

The alert list, with View on every row Tap View. Land on the message.

Every heading has its own chatroom, so every heading has its own level. One bell in the chatroom header cycles All notifications, then Mentions only, then Muted. There is no menu — needs an account.

One bell cycling three levels across three headings Mute one section and keep the next one loud.

Move a message into the page

Copy to doc writes the message into the document, under the heading that owns the chatroom. The line it leaves is dated and links back to the message. The message's files come with it — needs a document you can edit.

Reply in Thread does the other half. It turns a message into a new heading in the document, then opens that heading's chatroom — needs a document you can edit.

Both controls stay visible on this page, and the server refuses both writes.

A message in the chatroom, and the dated line it leaves in the page Say it in the chatroom, keep it in the page.

Filtered to this section? Show the whole document.

🗂️ The table of contents

Every heading in this document gets a row in the table of contents, at its own level, in document order. Scroll the page, and the row you are reading lights up. The table of contents scrolls itself, so that row stays in view. Click a row and the page scrolls to that heading — try it here.

The table of contents beside the page, one row per heading, with a folded section drawn as strips One row per heading. Fold from a row, and the page creases where the text went.

What lands in a row

A row carries the level of the heading and the text you typed. A heading with no text gets no row. An empty heading stays invisible to the table of contents until you name it.

The table of contents has its own column. Drag the divider between that column and the page to resize it — try it here. The width runs from 240px to 46% of the column and page together, and your browser remembers where you left it. That gesture needs a mouse.

The row's menu

Right-click a row for five entries — try it here. Chat Room opens the chatroom on that heading. Fold Section folds it, and folds back. Focus Section writes the heading text into the address as the only filter term. Copy link puts a link to that heading on your clipboard, and the copied link scrolls there on a cold page load.

Delete Section removes the whole section, body text and media with it — needs a document you can edit.

The menu is desktop only. The document title row at the top has none.

Folding

The chevron on a row folds its section — try it here. Body text, media and sub-headings hide, and the page creases where they were. The crease is sized by what it covers: two strips for a short section, up to four for a long one. Click a crease in the page to unfold it — try it here.

The chevron appears only on a heading that has sub-headings. A heading with body text alone folds from its right-click menu. 📜 What the history holds is one of those — try it here.

A fold changes no text. It is drawing, not editing, which is why it works on this locked page. Your folds are kept per document, in your own browser. Reload, and the page comes back creased where you left it. Nobody else sees them.

A filter folds sections for you. It saves your own folds first, and puts them back when you clear the filter.

Moving a section

Drag the grip on a row up or down to set the order — needs a document you can edit. Drag it sideways to set the depth. Each 24px step is one level, three steps at most, clamped between H1 and H6.

A drag card over the table of contents, with the landing line and the rest of the section stacked behind it Up or down sets the order. Sideways sets the depth. The whole section travels.

The whole section travels: the heading, its body text, its media, and every sub-heading under it. One undo step puts it back. The new level is written to the dragged heading alone, so sub-headings keep the levels they had.

The grip is desktop only.

Filtered to this section? Show the whole document.

🔍 Filter by word

The path after the document name is the filter. Nothing else holds that state.

Open /demo/spotify. The page folds down to the two sections that hold the word, and every hit is marked where it sits — try it here.

An address field holding two filter terms, beside a page folded down to the sections that match The path after the document name is the filter. Every hit is marked where it sits.

A section stays open when the word appears in its own text: the heading, plus the body under it, down to the next heading. Its parents stay open with it, and so does everything nested beneath it.

The match is a plain run of letters, and it ignores case. So fold also finds folding and unfold.

Two things follow. A term that matches a heading always keeps that section, so every section name on this page works as a filter. And the filter reads text, not subject matter.

One of the two sections spotify keeps is ▶️ Embeds. The other is this one, because the word sits here too.

Every word in this document is a filter term. Try /demo/account next. It keeps every claim that needs one, and this section again.

You do not have to use the links above. The filter button in the toolbar opens a search box that counts the matching sections as you type — try it here. Type any word on this page and watch the rest fold away. Whatever you type becomes the address, so you can hand that view to someone else.

Either word, or both

/demo/fold/drag keeps a section that holds either word. ?mode=or is the default, so naming it changes nothing.

/demo/fold/drag?mode=and demands both words in one section. Its result is always a subset of the other, because an intersection cannot return more.

Two sections hold both words: this one, and A section is a range. Their parent headings stay open above them.

The URL is the state

Active terms sit beside the title as chips. Each chip carries an × that drops that one term — try it here. Reset sits beside the chips, and clears every term and the mode with it. On desktop it fades in when you hover the row.

Copy what is in the address bar, and you have handed someone the view itself, not a description of it.

Filtered to this section? Show the whole document.

🧱 Why a section behaves this way

Every heading opens a section. Folding, the table of contents, the filter, and the chatroom on a heading all key off that one fact.

A section is a range

A section is not a container you put text into. It is a range the editor computes. It runs from a heading to the next heading at the same level or above it. When there is none, it runs to the end of the document.

Underneath, the document stays flat: one list of blocks, with nothing nested inside anything.

A flat numbered list of blocks, with two computed ranges marked beside it A section is a span the editor computes over one flat list of blocks.

That range is the unit the rest of the product acts on. The table of contents row stands for it. A fold hides it. A filter keeps it or drops it. A drag carries it.

📐 Size comes from rank

Heading size is computed, not chosen. Nothing in the markup says 18pt.

  1. Take the headings from one # to the next #.
  2. Collect the distinct levels among them, in order.
  3. Give the first 20pt, the last 12pt, and space the rest evenly between.
Distinct levels Sizes, in points Step between ranks
1 20
2 20 · 12 8
3 20 · 16 · 12 4
4 20 · 17.33 · 14.67 · 12 2.67
5 20 · 18 · 16 · 14 · 12 2

The block that holds this heading is the proof. It starts at 🧱 Why a section behaves this way and ends at the next #. That block uses two levels, so its sizes are 20pt and 12pt. This heading is a ##, so it renders 12pt.

Now take the block that starts at 💬 A chatroom on every heading. It uses three levels, so a ## inside it renders 16pt. Same tag, different size, because size answers how deep a heading sits inside its own block.

Filtered to this section? Show the whole document.

🔗 Links

Click a link on this page and a card opens under it, naming what sits on the other end — try it here. Going there takes a second click, on the card. You see where a link leads before you commit to leaving.

Out to the web

https://github.com/docs-plus/docs.plus is an ordinary external link. Its card fills with the page title and picture, held for 5 minutes. Edit and Remove sit on that card too — needs a document you can edit.

The card's title is itself a link, so one click on the title goes there — try it here. When the fetch fails, that title falls back to the raw address, and the link still works.

Middle-click a link to skip the card and open it in a new tab — try it here.

On mobile the card is a bottom sheet instead. Its four rows are Open link, Copy link, Edit link and Remove link. The first two work with no sign-in — try it here.

Addresses that are not web pages

A mailto: or tel: link has no page to fetch. The address becomes the title, and the icon names the kind. The pad maps 40 schemes this way: mail, phone, maps, video calls, and app links.

Back into the document

A link into this document never touches the network. It resolves on the spot, and the card shows a destination chip. Five kinds exist: This document, a quoted heading, Chat, Filtered view · N terms, and Version N.

/demo/crease is such a link. Its card carries a Filtered view · 1 term chip. Click the chip and the page folds in place — try it here. No reload, and no new tab.

Filtered to this section? Show the whole document.

🎬 Media

Nine kinds of media sit in the body text: pictures, uploaded video, uploaded audio, and six embed providers.

Images

A picture sits in the run of the text, not in a gallery beside it. While a remote picture loads, a grey shell holds its place.

Hover a picture and its toolbar appears — needs a document you can edit. The toolbar checks edit rights first, so this page shows the pictures and no toolbar.

Eight handles ring the picture with that toolbar up. The four side handles change one axis. The four corner handles change both, and holding Shift on a corner keeps the shape — needs a document you can edit. Nothing goes below 160px wide or 80px high.

Video and audio

An uploaded file plays where it sits. Press play on the video and then on the audio below it — try it here.

Neither is built by pasting an address, the way the embeds are. Both arrive through the insert panel, at up to 10,485,760 bytes — needs a document you can edit.

/demo-assets/demo-video.mp4

/demo-assets/demo-audio.mp3

Clip credits: the video is Big Buck Bunny, © Blender Foundation, CC BY 3.0. The audio is Example.ogg from Wikimedia Commons, CC BY-SA 3.0.

▶️ Embeds

Six providers render in place rather than as links: YouTube, Vimeo, Loom, SoundCloud, Spotify, and X. Paste a provider address into the body and the player is built on the spot — needs a document you can edit. No menu, and no dialog.

An X post has no drag grip. Its width comes from presets instead — needs a document you can edit.

A Spotify player will not go under its own height. A single track floors at 152px, and every other kind floors at 352px.

https://www.youtube.com/watch?v=aqz-KE-bpKQ

https://vimeo.com/76979871

https://www.loom.com/share/e5b8c04bca094dd8a5507925ab887002

https://soundcloud.com/forss/flickermood

https://open.spotify.com/track/11dFghVXANMlKmJXsNCbNl

https://x.com/jack/status/20

Filtered to this section? Show the whole document.

✍️ Writing

Formatting shows a reader which decisions matter.

Marks that mean something

Reach for a mark when it changes meaning, not when a sentence feels flat. Bold names the one thing a skimming reader must not miss. Italic marks a term or a title. Strikethrough keeps the record when a decision moves. ==Highlight is a note to self: a section still wearing one is still owed work.==

Four marks, each on its own line Bold, italic, strikethrough, highlight: four marks, four jobs.

Each mark has a toolbar button and a key, from ⌘+B for bold to ⌘+⇧+H for highlight — needs a document you can edit. Highlight also survives Markdown, as ==text==.

There is a fifth mark, underline, and it is the one to reach for least. On a page full of links, an underlined word claims to be one.

Code stays code

Inline code sets a flag, a path, or a value apart from the prose: --frozen-lockfile, mode=and, heading-scale.ts. Type a backtick pair, or press Mod-e — needs a document you can edit.

For anything longer than a phrase, use a fenced block and name the language. The pad colours exactly nine: html, css, js, ts, markdown, python, yaml, json, and bash.

bun run --filter @docs.plus/webapp dev

Filtered to this section? Show the whole document.

Lists, quotes and rules

Three kinds of list, and the choice carries meaning. Pick the one that matches the thing.

A bullet list holds items with no order. Reordering it changes nothing.

  • A heading opens a section
  • A section is a range
  • A range is what folds

A numbered list holds steps that run in order. Reordering it breaks the instructions.

  1. Open a heading from its table of contents row
  2. Read what is under it
  3. Answer in the chatroom beside it

A checkbox list tracks work that is not finished. A tick is saved for everyone reading — needs a document you can edit.

  • Seed the chatrooms with a real conversation
  • Draw the version history picture
  • Put every illustration in the page

A quote sets words apart from your own.

A section is not a container you put text into. It is a range the editor computes.

Type four dashes and the editor draws a rule across the page — needs a document you can edit.


A rule ends a passage. It carries no text, so a filter never keeps a section for it.

📜 What the history holds

Every save is a whole snapshot of the document, with a rising version number. Typing is saved after 10 seconds of quiet, and forced after 60 seconds when you keep going.

Open the history from the clock button in the document header — needs an account.

Versions arrive grouped into sessions, then into days titled Today, Yesterday, or a date. A session breaks after a gap of two minutes. A row carries the faces of the people in that version. It also carries a badge naming where the version came from: API, Checkpoint, Restored, Pre-restore, or Migration. An ordinary live save gets no badge.

Click a version and it renders in a read-only editor, so reading an old draft changes nothing. Heading chat is hidden while you read one. Inside the view you can compare two versions, copy a link to one, and print the one you are reading — needs an account.

Compare marks what was added inline, and keeps what was removed as struck-through text. The authors panel colours each block by whose text sits there now.

Restore puts an old version back for everybody, and it is offered on every version except the latest — needs a document you can edit. A restore writes a safety copy of the current text first, and that copy carries the Pre-restore badge.

After 30 days, old automatic saves are thinned to one per day. Each day's newest save survives. A version you named yourself is never thinned.

The history can name who wrote a run of text. It can never name who deleted one. A restore, an import, or an API write carries no author, so the badge names the operation instead.

A signed-in rename of the Pad title posts a notice in the document chat. History paints the latest of those notices above the editor. That notice is not a version. A signed-out rename posts nothing.

Filtered to this section? Show the whole document.

Saved versions grouped by day, with faces, badges, and no Restore on the newest

📤 Import and export

The gear button opens Import & export. Four export rows sit there: Word .docx, Markdown .md, OpenDocument .odt, and Print / Save as PDF — try it here. Every visitor is signed in anonymously on arrival, so an export asks for nothing.

An export is built from the last saved version, so a very recent edit may be missing from it.

Import takes .docx and .md only, up to 10,485,760 bytes, and Markdown stops at 65,536 characters. It replaces the whole document, so this read-only page hides it — needs a document you can edit. A Markdown replace keeps pictures at their natural size, and a lone video, audio, or embed URL becomes a player.

Filtered to this section? Show the whole document.

🔐 Who can open this document

A document carries two flags: Private and Read-only. Private is owner-only. A signed-out visitor is asked to sign in, and any other signed-in person is refused. Read-only stops everyone but the owner from editing, and this page is read-only.

A document with no owner is open. Anyone may change its Pad title, signed in or not. Typing does not make that person the owner. Private and Read-only do not turn on until the document has an owner. This page already has an owner, so only they can rename it.

The two flags are never on together. Turning Private on clears Read-only in the same request.

The header shows a chip for each flag. Open the settings panel to see the owner, and the two flags as badges — try it here. Only the owner sees them as switches.

Share sits in the header, signed in or not — try it here. The link it offers is the address you are on. So it carries the heading you jumped to, the filter you are in, or the version you are reading.

A share link carries no permission. There is no token, no expiry, and no grant to one person. Who can open a document is decided by the two flags, and by who you are signed in as. On a private document the dialog hides the link, and says to turn Private off in Settings.

Filtered to this section? Show the whole document.

🚀 Make your own

/new gives you a blank document with a random name, and you can write in it — try it here. Nothing to install, and no sign-up to start.

Everything above then runs the other way round. You can type, move a section, insert media, and replace the whole document with a file. Posting in a chatroom asks you to sign in first — needs an account.

The code is open: https://github.com/docs-plus/docs.plus.

Filtered to this section? Show the whole document.