Skip to content

Match Microsoft Whiteboard highlighters and note corners, and import lists and GIFs (1.7.3) - #174

Merged
marcosqlbi merged 2 commits into
mainfrom
fix/ms-whiteboard-highlighter-width
Sep 30, 2026
Merged

marcosqlbi merged 2 commits into
mainfrom
fix/ms-whiteboard-highlighter-width

Conversation

@marcosqlbi

@marcosqlbi marcosqlbi commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

All 19 sample boards were compared with Edge tile by tile. A scratch tool imported each board and drew it through BoardRasterizer in tiles of 1600 × 1000 canvas pixels, headless Edge drew the same rectangles of the page, and a blurred color difference ranked the tiles. Two differences remained beyond those already documented.

Highlighter width. The tip here is 4t wide and 2t tall, while Microsoft's is narrow and tall, twice as tall as wide on most boards and nearly square on some. The importer set t from the tip's height alone, which matched horizontal highlights and drew vertical ones up to four times too wide. Microsoft's tip is now measured per stroke: its width from what it adds to the centerline's width, and its height as the mean of the upper quartile of the outline's vertical edges and what the tip adds to the centerline's height, because the first reads short and the second long. The thickness is the least-squares match between the two tips' band widths over the stroke's directions, taken over runs at least one tip long so a hand's jitter does not count as steep runs. Horizontal strokes keep t = h / 2; vertical ones get t = w / 4.

Across the 18 teaching boards, the pixels that differ from Edge fell from 1.33% of the ink to 0.41%, and the tiles scoring above 3% from ten to three, all dense scribbles that run both ways and that Microsoft darkens where they overlap.

Note corners. Notes and note grid panels were rounded rectangles, much rounder than Microsoft's slightly rounded notes. They now use the square-cornered Rectangle added in 1.7.2.

The import doc records the new width rule, the comparison, the list and GIF markup, the guide's import figure and its caption no longer show rounded notes, and the export page drops the rounded-notes limitation. A new Core smoke test covers a vertical highlighter. The solution builds clean and both harnesses pass.

Lists and GIFs. None of the samples holds a list or a GIF, so their markup was read from the Microsoft Whiteboard web client, whose code is public at whiteboard.cloud.microsoft. Its type registry names every data-whiteboard-type an export can hold; the import doc now records the list. A GIF (AzureGif) is converted by the same function as an older picture (AzureImage), so it is read as a picture. Text, bulleted, and task lists (VerticalList, VerticalBulletList, VerticalCheckboxList, UnknownList) are drawn by ListContainerComponent: a title, a column heading, and one listChild per item with its icon and text. They become Markdown text containers titled List, with the list's title in bold, a Markdown list for bullets, and ☒ or ☐ for a done or open task, because the board's Markdown has no task lists and WPF draws ☑ as an emoji. Assignees and likes are left out. The widths come from the web client's list constants. A new Core smoke test builds a page the way that component draws it; all 19 samples still import unchanged.

The version goes to 1.7.3, with a CHANGELOG entry.

🤖 Generated with Claude Code

marcosqlbi and others added 2 commits September 30, 2026 14:07
Compared tile by tile with Edge, vertical and diagonal highlighter strokes came out up
to four times too wide, and notes had much rounder corners than Microsoft draws.

A highlighter's thickness is now the least-squares match between the two tips over the
stroke's directions, with Microsoft's tip measured per stroke. Notes and note grid
panels use the square-cornered rectangle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Text, bulleted, and task lists become Markdown text containers titled List, with the
list's title in bold and a crossed or empty box for each task. GIFs are read as
pictures, as older pictures are. Both were reported as left out.

The markup comes from the web client's ListContainerComponent and its list constants,
because no sample holds a list or a GIF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@marcosqlbi marcosqlbi changed the title Match Microsoft Whiteboard highlighter widths and note corners (1.7.3) Match Microsoft Whiteboard highlighters and note corners, and import lists and GIFs (1.7.3) Sep 30, 2026
@marcosqlbi
marcosqlbi merged commit 17c3b33 into main Sep 30, 2026
5 checks passed
@marcosqlbi
marcosqlbi deleted the fix/ms-whiteboard-highlighter-width branch September 30, 2026 13:49
marcosqlbi added a commit that referenced this pull request Sep 30, 2026
…3) (#175)

Two more object types from boards made with the Windows 10 app, both
reported as left out until now. Neither appears in the 19 sample boards,
so their markup was read from the Microsoft Whiteboard web client's
public code, as the lists and GIFs were in #174.

**Ink tables.** A table drawn with ink ("ink to table") has no
`toWhiteboardType` registration, so the web client falls back to
`data-whiteboard-type="Unknown"`. `TableComponent` draws it as a CSS
grid: `grid-template-columns` and `grid-template-rows` give each track
as `minmax(<size>px, auto)`, and each `div.inkCellContainer` has a `4px
solid` border in the table's color with a `-2px` margin, so the border
sits over the grid line. Inside a cell, a `div` shifts the ink by the
column's and row's negative offsets, and each ink group sits in a
collection child `div` with its own `left`, `top`, and `transform`,
holding the usual ink group SVG. The importer now looks inside an
`Unknown` object for `div.inkTableContainer`: each cell becomes a
square-cornered rectangle with a 4-unit outline, then the cells' ink
comes on top, read by the existing ink reader through an anchor composed
from the table, the cell's corner and shift, and the group's place. An
`Unknown` object without a table is still reported. The web client draws
a placeholder instead of the grid when its `EnableTables` flag is off,
and that case stays reported as `Unknown`.

**Legacy stickers.** `LegacySticker` converts to the same image content
as a picture, with a caption aspect: a `div` moved from the picture's
corner by its `transform`, at `font-size` 30 with the family and weight
in its style, holding a text editor as wide as the caption and smaller
when the text shrank to fit. The sticker becomes its picture and a label
centered across the caption's box, turned and scaled with the sticker.

A new Core smoke test builds a page the way those components draw them:
a two-by-two table with ink in one cell, a scaled sticker with a
caption, and an `Unknown` object without a table. The page was also
drawn through the application's own renderer. All 19 samples still
import unchanged. The solution builds clean and both harnesses pass.

The import doc records both, the site's export page, the guide, and
README mention tables, and the 1.7.3 notes, not yet released, cover
them. The version stays 1.7.3.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
marcosqlbi added a commit that referenced this pull request Sep 30, 2026
Templates from boards made with the Windows 10 app (`LegacyTemplate`)
were reported as left out. No sample holds one, so the markup was read
from the Microsoft Whiteboard web client's public code, as the lists,
tables, and stickers were in #174 and #175.

The web client converts a legacy template into a collection, drawn by
`CollectionComponent`: one `div.topLeft` child per item with its own
`left`, `top`, and `transform`, holding the content of one object
without the anchor around it. The first child is the title, a
`textbox.templateTitle`; the exported stylesheet draws it as a white
panel 60 pixels tall with a 4-pixel `#0C34FA` bar on top, 6 and 8 pixels
of padding, and bold 24-pixel text. Every child is moved by the same
offset so that none sits left of or above the title. When the web
client's `EnableTemplates` flag is off it draws a placeholder instead,
and that case stays reported.

The importer reads the title as a filled rectangle for the bar and a
bold label, which is what shows on a white board. Each other child is
recognized by what it holds and read by the existing reader for that
kind (ink, a note, a note grid, a list, a text box, a picture, or a
shape including the older ellipse and polygon) through the child's
anchor composed with the template's. A picture or a shape moves by half
its size, because a child is placed from its corner while those are
centered on their anchor at the top level. A child of any other kind is
counted as a template item in the message after the import.

A new Core smoke test builds a template with a title, a note, a picture,
scaled ink, an older polygon, and a child of an unknown kind, plus a
template drawn as a placeholder, and checks every position and count.
The page was also drawn through the application's renderer. All 19
samples still import unchanged. The solution builds clean and both
harnesses pass.

1.7.3 is released, so the version goes to 1.7.4, with a CHANGELOG entry;
the import doc and the site's export page describe templates.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
marcosqlbi added a commit that referenced this pull request Sep 30, 2026
Loop components (`LoopObject`, and `HostedFluidObject` or
`GroupFluidObject` holding one) and apps made by Copilot
(`AppIframeHost`) were reported as left out. No sample holds either, so
the markup was read from the Microsoft Whiteboard web client's public
code, as for #174 to #176.

The export keeps neither kind of content. Its export code
(`replaceLoopIFramesWithPlaceholders` and
`replaceAppFrameIFramesWithPlaceholders`) does this:

- **Loop component.** The export replaces the content of each
`div.loopParentDiv` with an `a` to the component's `data-loop-url`
around the picture `LoopExportThumbnail.svg`, which it does not embed.
The importer makes it a Markdown text container titled *Loop* with a
link to that address, as wide as the component, or the words *Loop
component* when there is no address. `HostedFluidObject` and
`GroupFluidObject` are read the same way when they hold a Loop element
and are reported otherwise.
- **App.** `AppIframeHost` is an app that Copilot generated, running in
a sandboxed `iframe`, not a video. The export replaces the frame with a
`#faf9f8` panel and keeps the `div.appFrameContainer` with its size and
its header, whose title reads *App* in the board's language; the app's
name, code, and state are not in the export. The importer makes it a
rectangle of that size in the panel's color with the header's title as
its text, so the space it took stays on the board.

Both are placed by the anchor's alignment: `Anchor` now records whether
its `center` class is present.

A new Core smoke test builds a sized Loop component with its link, a
centered one without an address or a size, an app frame, and a
`HostedFluidObject` without a Loop element, which is still reported. All
19 samples still import unchanged. The solution builds clean and both
harnesses pass.

1.7.4 is not released yet, so this joins its notes as a second entry and
the version stays 1.7.4. The import doc and the site's export page
describe both.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
marcosqlbi added a commit that referenced this pull request Sep 30, 2026
#178)

Azure DevOps work items (`WorkItem`) and frames that Copilot draws
around the objects it groups (`Frame`) were reported as left out. No
sample holds either, so the markup was read from the Microsoft
Whiteboard web client's public code, as for #174 to #177.

**Work items.** `WorkItemConnected` draws a `div.WorkItem` card, 220 ×
145 pixels in the exported stylesheet, when the web client's
`EnableWorkItems` flag is on. A `span.WorkItemTextField` holds a
`strong` with the type's `img.WorkItemIcon` (its `alt` names the type)
and the ID, then the title; after it come the assignee and, in
`span.WorkItemText`, the localized word *State* and the state. The
importer makes it a Markdown text container titled *Work item*: the type
and ID in bold before the title, then the assignee and the state, as
wide as the card.

The title follows `</strong>`, and `HtmlTags` kept only the text that
follows a start tag. It now also yields the non-blank text that follows
an end tag as a tag named `#text`. No reader matches that name, and all
19 samples import with the same items before and after the change.

**Copilot frames.** The frame component draws a
`section.whiteboardFrame` with its size and a theme class (neutral,
blue, green, purple, orange) when `EnableFrames` is on. The stylesheet
gives it a 3-pixel border, a translucent fill, 14-pixel corners, and a
44-pixel title bar with bold 15-pixel text. The importer makes it a
rectangle with the theme's border and fill and its title as a bold label
in the theme's text color. It is square rather than rounded, because 14
pixels on a frame is close to square while the board's rounded rectangle
scales its corners with the shape and crowded the title. The sources are
left out: the export keeps only the first one's name in a pill, and the
list is a popover drawn only when open.

A work item or a frame drawn as a placeholder is still reported.

A new Core smoke test covers a work item card, a blue frame with a
source pill, and both drawn as placeholders; the page was also drawn
through the application's renderer. The solution builds clean and both
harnesses pass.

1.7.4 is not released yet, so its notes entry now covers work items and
frames, and the version stays 1.7.4. The import doc and the site's
export page describe them.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
marcosqlbi added a commit that referenced this pull request Sep 30, 2026
Custom objects (`CustomElement`) were the last object type in the
Microsoft Whiteboard web client that the importer reported as left out.
No sample holds one, so the markup was read from the web client's public
code, as for #174 to #178.

A custom object is a widget from a plug-in, named by its kind in
`data-custom-widget-kind`. The web client registers only two examples,
`example.counter` and `example.embed`. For any other kind, and for every
kind when its `EnableCustomElement` flag is off, it draws a gray box
that fills the object and says the widget is not available or disabled.
The box is at least 120 × 80 pixels, and its own size is `100%`. The
export therefore holds the box and the kind, not the widget.

The importer makes a custom object a rectangle of its size in the gray
the web client draws, with *Custom object* and the kind as its text. Its
size is the first one given in pixels, because a percentage is the
widget filling its box; without one it takes the smallest box. A custom
object without a kind is still reported.

A new Core smoke test covers a sized, disabled `example.counter`, a
centered widget with only percentage sizes, and one without a kind. All
19 samples still import unchanged. The solution builds clean and both
harnesses pass.

With this, every `data-whiteboard-type` the web client names has a
reader; what stays reported is content drawn as a placeholder or missing
the element that identifies it. 1.7.4 is not released yet, so its notes
entry now includes custom objects and the version stays 1.7.4. The
import doc describes them.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
marcosqlbi added a commit that referenced this pull request Sep 30, 2026
Tables and templates that the web client drew as placeholders were
reported as left out, and so were work items and other kinds drawn the
same way. No sample holds one, so the markup was read from the Microsoft
Whiteboard web client's public code, as for #174 to #180.

When a feature flag is off for whoever exports the board
(`EnableTables`, `EnableTemplates`, `EnableWorkItems`, and others), the
web client draws its placeholder component instead of the content: a
`div.unknownObject` holding a title and a description in the board's
language in `div.unknownObjectText`, and nothing of the content. The
exported stylesheet makes it at least 448 × 170 pixels, white, with a
shadow and no border.

The importer now checks every object for a placeholder before reading it
by type. A placeholder becomes a white rectangle of the placeholder's
size with its two lines as its text, and a light outline, because the
shadow that marks it on the page does not carry over. A template whose
children were drawn is still read child by child, and a child that is a
placeholder becomes the same box at its place. Placeholders are no
longer reported as left out, because the box and its message are what
the page shows and they stay on the board.

The export holds no more than that, so a placeholder stays a
placeholder. The import doc lists this among what is lost, and the
site's export page says so.

A new Core smoke test covers a table drawn as a placeholder, a centered
template drawn as one, and a template whose children were drawn with one
child drawn as a placeholder. All 19 samples still import unchanged. The
solution builds clean and both harnesses pass.

1.7.4 is not released yet, so this adds an entry to its notes and the
version stays 1.7.4.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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