Skip to content

Add Workspace Notes plugin - #89

Open
preetvadaliya wants to merge 20 commits into
mit-cml:mainfrom
preetvadaliya:add-workspace-notes-plugin
Open

preetvadaliya wants to merge 20 commits into
mit-cml:mainfrom
preetvadaliya:add-workspace-notes-plugin

Conversation

@preetvadaliya

@preetvadaliya preetvadaliya commented Sep 8, 2026 •

Copy link
Copy Markdown

Adds Workspace Notes — sticky notes for a Blockly workspace: draggable,
resizable, colour-coded paper with a title, an author and a stacking order.

The parts of a note — collapse button, title, delete button, body, author and date, resize handle — and below it a locked note with a padlock, no delete button and no resize handle

Notes extend Blockly's own workspace comments, so dragging, resizing,
selection, keyboard navigation and undo all keep working unchanged. What is new
is the title, the colour, pinning, locking, stacking order, author and dates,
and a versioned save format.

Six notes: a named note, one not named yet, a collapsed note, a pinned note, a selected note, and a locked note showing a padlock and no delete button

Using it

import {WorkspaceNotes} from '@mit-app-inventor/blockly-workspace-notes';

const workspace = Blockly.inject('blocklyDiv', {toolbox});
new WorkspaceNotes(workspace).init();

Right-click the workspace to add a note, click a note's title to rename it, and
right-click a note to recolour, pin, lock, collapse or restack it.

Locking

A locked note is read-only. Its text, title and colour cannot be changed, and
it cannot be deleted, resized or copied. It shows a padlock in the title bar
and loses the bin beside it. It can still be moved, collapsed, restacked and
read.

Who may lock or unlock a note is the app's decision, through canToggleLock —
so a teacher can lock an instruction note that a student cannot unlock. That
gates the menu rather than the model, so it stops accidents and states intent;
anything that has to hold should be checked where the workspace is saved.

Pinning and locking stay independent: pinning decides where a note sits,
locking decides whether it can change.

Saving

Notes save under their own workspaceNotes key beside blocks, with a version
number so older files can be upgraded as they load. Only what differs from the
default is written, so files stay small and diff cleanly. Workspaces saved
before this plugin — with plain Blockly comments in them — still open, each
comment becoming a note. XML round-trips too.

What is in the PR

  • The plugin under blockly-workspace-notes/, written in TypeScript, with 121
    tests
  • Documentation in blockly-workspace-notes/docs/
  • A CI workflow alongside the existing one

One thing worth flagging

This needs Blockly ^13.2.1, while main is on ^11.2.2. Nothing conflicts
inside the repo — it is a separate package with its own CI job — but the two
plugins' peer ranges cannot both be satisfied in a single app. Happy to
retarget this at update-to-blockly-13 if you would prefer.

A locked note is read-only and cannot be deleted, resized or copied. It
shows a padlock in the marker slot, taking it from the pin, and loses the
delete button from its bar - which is also what enforces it, since core's
own button does not check isDeletable before disposing of the comment.

Locking drives editable and deletable; pinning keeps movable. Neither
writes the other's flag, so the two compose.

A host decides who may lock and unlock through canToggleLock, held per
workspace rather than captured at registration so two workspaces cannot
share one set of permissions.
The diagram never labelled the title, and predated the marker slot. It now
has two sections: the parts of an ordinary note, and a locked one beside
callouts for the padlock and for the two controls that go missing.
Recognise a note by a mark the mixin puts on it rather than by instanceof,
so utils/guards no longer imports the note classes - that import was what
closed the loop. Raising a note now goes through a bringToFront seam, so
stacking needs nothing from model/ at all.

The cycle was benign but dishonest: the production bundle flattens those
modules into one scope while the test bundle keeps them apart, so a cycle
that broke would have broken only in dist/. A test now walks the graph.

The mark is a Symbol.for, so two bundled copies of the plugin recognise
each other's notes, which instanceof never could.
layout.ts said nothing in the chrome should introduce an ad-hoc number,
while css.ts carried ten of them - the card border and its pinned
doubling, the glyph stroke in three places, two fades, the focus ring and
the selection weight.

Three comments named the wrong source: the line box is core's field
border rect, not its icon, which is 17; the scrollbar's prose described
arithmetic giving 5 for a value of 8; and the footer's 11 is px where
core's identical-looking 11 is pt.

The footer glyph now derives from the line box. The font size deliberately
does not - three quarters of the body's 14.667px is 11.00025, which would
reach the stylesheet verbatim.

dist/index.js is byte-identical, so nothing moved.
They had drifted apart and away from the source: one file gave the title
bar a height of 35 and the other 36, the collapsed card was a pixel short,
the two disagreed about where the date sits, and the resize handle was
drawn eight pixels above the box it occupies.

The states diagram drew no resize handle at all, which left the locked
note's missing one looking like an omission rather than the point.

Both now use the straddling stroke the card really has, carry data-part
hooks, and are checked against the constants by a test that ignores
colour, wording and artwork so they stay free to redraw.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant