Create the picture_frames table
One row per Picture Frame widget a member has arranged on their launchpad. A table rather than a preference blob because the photos and videos in a frame are i...
The schema, rights and storage domain behind frame widgets
A Picture Frame is a member's own photos and videos on their own launchpad, and a member may hang several. This is everything the platform needs to hold one. P...
Put the four member shortcuts on one row
are one class of control, so they now share one row: the launchpad's top-right corner reads as a single strip rather than a two-line stack. styling; it uses th...
Position and colour for a pane's logo, and always let a member open the editor
THE EDITOR WAS UNREACHABLE FOR ANYONE THE ROLL HAD NOT DECIDED FOR. `brandOf` answered null until a mark existed and the control was gated on the answer, so a m...
Paint the master notes in the organisation's colours
"Our Master Note" is the organisation's PRIMARY and "My Master Note" its SECONDARY, each as a pastel — an 18% tint on white. The pair reads as a set at a glance...
Put the two master notes on every launchpad
"My Master Note" and "Our Master Note" are panes in the same sortable grid as everything else — dragged, resized and ordered like any other pane — and neither c...
Serve, provision and protect the two master notes
GET /api/notes/master answers both and provisions whichever is missing. PUT /api/notes/master/org writes "Our Master Note", addressed by organisation rather tha...
Add the master-scope columns to notes
MASTER_SCOPE ('MEMBER' / 'ORG' / NULL) and ORGANIZATION_ID, plus the two indexes the entity declares and a partial unique index per scope so one live master not...
Model the master notes and the right that guards the org's
A master note is the one note a member — or an organisation — always has. "My Master Note" is provisioned per membership; "Our Master Note" is one per organisat...
Brand a third of a member's panes with the org logo, and let them tune it
A third of a member's panes wear the same faint org mark /leads and /calendar carry, at pane scale. Which panes, and how, is rolled once and kept. WHAT THE ROL...
Edit in Classic Mode, and redraw when it saves
The form-fillable editor can now branch out to LibreOffice. The server derives the classic version on the way if the document has not got one, which is the poin...
Give the Linux options a Tux who hops
The icon beside "Linux Desktop" and "Dev Machine" was an 18.6 KB static penguin. It is now a 1.4 KB vector one who bounces, built from flat ellipse geometry so ...
Announce a classic save to the member who made it
A WOPI save on a CLASSIC version now publishes docs.classic-saved.{memberId} so the Kamo PDF editor, if it is open on the form-fillable version, can redraw the ...
Relay "the classic version was just saved" to the editor
DocsService publishes docs.classic-saved.{memberId} after a WOPI save lands on a CLASSIC version; this puts it on /topic/docs/saves/{memberId} over the STOMP co...
Save As branches a new original, not a new copy of one
PutRelativeFile was unimplemented, so "Save As" in the LibreOffice editor did nothing. It now does what the member means by it: the bytes become a brand-new ORI...
Move existing designs onto FORM_FILLABLE rows
Before versions existed, putting form fields on a document attached an esign_templates row straight to the original, and the Modified tab was "originals that ha...
Seed 13% of a member's panes with a colour, and offer glass everywhere
TWO THINGS. A SHARE OF THE LAUNCHPAD ARRIVES COLOURED. On a member's first load — once ever — each pane gets a 13% chance of a random palette colour. Enough th...
One Modify menu, and Modified as its own records
"Open in Docs" and "Edit PDF" are gone. Each row has Modify, which offers "Edit In Form Fillable Mode" always and "Edit in Classic Mode" only where the LibreOff...
Derive modified versions instead of editing originals
Opening a document in the LibreOffice editor no longer hands over the original. It derives that document's CLASSIC version — once, reused every time after, and ...
Add imgs.modified_kind and imgs.original_img_id
Both stay nullable — null kind means the row is an original, so there is nothing to backfill and no NOT NULL step, unlike the usual column-add shape. The parti...
Split a document into its original and its modified versions
imgs.modified_kind + imgs.original_img_id. Null kind means the row IS an original — the bytes a member uploaded — which is the correct reading for every row tha...
Resizable, recolourable panes — and pinned notes join the grid
FOUR THINGS, one grid. CUSTOM SIZING. A widget can now declare `custom`, and all five do. The registry's width and height stay the DEFAULT — custom adds a hand...
Three more widgets — unread mail, leads needing action, today's timecard
The widget system paid for itself: each of these is a registry entry and a component, inheriting the grid, the drag, the ordering, the per-member show/hide, the...
Seed the mail and lead widgets, and add a universal seed for the timecard one
The mail and lead widgets follow their data rights exactly as the calendar ones do — ACCESS_MAIL and VIEW_LEADS — so a holder gains no data they could not alrea...
Serve the home-page unread digest from the index, never from IMAP
A widget on the home page is on screen for everyone all day, whether or not anybody is reading mail — and a member's IMAP connections are a shared budget of ten...
Three more home widgets, and the one that cannot have a parent
Unread mail hangs off ACCESS_MAIL and leads-needing-action off VIEW_LEADS, both by the same rule as the calendar widgets: a widget is gated by the right that go...
Put live widgets on the launchpad, starting with two calendar strips
A widget is a live pane that shares the grid, the drag and the ordering with the app cards rather than sitting beside them. Adding the next one is a single regi...
Seed the calendar widgets to everyone who can already read the calendar
Both widgets show a member exactly what /calendar already shows them, so a calendar reader gains no data they could not reach — which is what makes this a safe ...
Serve and announce the home-page calendar widgets
CalendarWidgetService answers what a widget actually needs and the calendar page hasEarlier/hasLater so an arrow greys out instead of offering an empty page. Th...
Give every home widget a five-right policy, and page the calendar for it
Ten rights arrive as two five-right blocks — one per widget — hanging off VIEW_CALENDAR, because a widget must be gated by the right that governs the data it sh...
Add the Policies tab, and stop offering chats the backend refuses
Four views over one draft ruleset, because there are four different questions and no single layout answers them all. Rules is what each rule does, Matrix is wha...
Create the chat policy schema
Six tables: the versioned ruleset, its rules and their clauses, the reply grants behind "reply only while their window is open", and the trail of what the polic...
Annotate the member roster with its chat policy verdict
SecurityService serves the roster the chat UI is built from, so it has to answer with the same verdicts MediaService enforces with -- one shared evaluator, or t...
Enforce the org chat policy, and close two holes it sat on
Applies the policy at the three places a conversation can start or continue: session creation, member adds, and the send path. A room is not policed pair by pai...
Add the org chat policy engine
Decides whether one member may open a conversation with another, and whether they may send into one that exists. Rules are an ordered list over ordered pairs an...
Reorder the tabs and paint Binders and Uploads
The strip reads Originals | Modified | Common | Binders | Uploads, which puts the three views of the documents together and the two destinations after them. Bin...
Say when a selected document could not be added
The Selected Docs binder skips a document it cannot legally hold rather than failing the whole sync, which is right — but it did so silently, so a binder holdin...
Fold the Modify tab into Binders
The strip is now Originals | Binders | Modified | Common | Uploads. Modify is gone: everything it did — arrange the checked documents, preview them, merge them ...
Serve and sync the Selected Docs binder
POST /api/docs/binders/scratch fetches the caller's Selected Docs binder for one collection, creating it on first use, and replaces its documents with the grid ...
Like what you see shipping?
Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.