feat(docx-editor): blue page with a pilcrow, matching pdf-editor's badge
The mark now shares pdf-editor's silhouette, corner geometry and weight, in blue against its red, so the two editors read as a pair in the dock. It carries a pilcrow rather than a format label: at 16px "DOC" is a smudge while the paragraph mark is still a glyph, and it says word processor rather than file extension. Drawn as paths, not <text>. An icon that needs a particular font installed to make sense is an icon that eventually renders wrong somewhere. Still nothing of Microsoft's: their blue sheet carries a white W, and the page-with-a-folded-corner is the universal document glyph rather than anyone's property. Also adds docs/DESIGN-one-extension-catalogue.md, which writes down the larger point this kept bumping into — that bundled and community extensions are two systems for one kind of thing, with two trust rules and two lists in Settings, and that "ships by default" should be a line of configuration rather than a separate distribution path. Proposed only; the awkward part is keeping a fresh offline install usable, which the note answers by treating the shipped copies as a pre-seeded cache that still verifies like everything else.
|
|
@ -4,7 +4,7 @@
|
|||
"version": "0.1.1",
|
||||
"description": "Open, edit and save Word documents (.docx) in a full Theseus tab, or export them as PDF. Ribbon-style formatting, tables, lists, images and links; headers, footers, footnotes, page setup and the document's own styles are carried through a save untouched.",
|
||||
"author": "Silent Mode",
|
||||
"icon": "data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2032%2032%22%20role%3D%22img%22%20aria-label%3D%22Word%20editor%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22dx-page%22%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%220%22%20y2%3D%221%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23ffffff%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23e9edf4%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Cpath%20d%3D%22M6.5%202.5h12l7%207v20a1%201%200%200%201-1%201h-18a1%201%200%200%201-1-1v-26a1%201%200%200%201%201-1z%22%20fill%3D%22url(%23dx-page)%22%20stroke%3D%22%232a3242%22%20stroke-width%3D%221.5%22%20stroke-linejoin%3D%22round%22%2F%3E%3Cpath%20d%3D%22M18.5%202.5v7h7%22%20fill%3D%22%23cfd8e6%22%20stroke%3D%22%232a3242%22%20stroke-width%3D%221.5%22%20stroke-linejoin%3D%22round%22%2F%3E%3Crect%20x%3D%229.5%22%20y%3D%2213%22%20width%3D%229%22%20height%3D%222.2%22%20rx%3D%221.1%22%20fill%3D%22%230ac18e%22%2F%3E%3Crect%20x%3D%229.5%22%20y%3D%2217.6%22%20width%3D%2213%22%20height%3D%221.6%22%20rx%3D%22.8%22%20fill%3D%22%2397a3b6%22%2F%3E%3Crect%20x%3D%229.5%22%20y%3D%2221%22%20width%3D%2213%22%20height%3D%221.6%22%20rx%3D%22.8%22%20fill%3D%22%2397a3b6%22%2F%3E%3Crect%20x%3D%229.5%22%20y%3D%2224.4%22%20width%3D%228.5%22%20height%3D%221.6%22%20rx%3D%22.8%22%20fill%3D%22%2397a3b6%22%2F%3E%3Cg%20transform%3D%22translate(20.4%2019.2)%22%3E%3Ccircle%20cx%3D%224.6%22%20cy%3D%224.6%22%20r%3D%225.4%22%20fill%3D%22%230ac18e%22%2F%3E%3Cpath%20d%3D%22M6.6%201.9H3.9a1.85%201.85%200%200%200%200%203.7h.8M5.3%201.9v5.6M6.6%201.9v5.6%22%20fill%3D%22none%22%20stroke%3D%22%230a0f16%22%20stroke-width%3D%221.15%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E",
|
||||
"icon": "data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cpath%20d%3D%22M6%202h8l6%206v12a2%202%200%200%201-2%202H6a2%202%200%200%201-2-2V4a2%202%200%200%201%202-2z%22%20fill%3D%22%232563eb%22%2F%3E%3Cpath%20d%3D%22M14%202v6h6z%22%20fill%3D%22%231e40af%22%2F%3E%3Cg%20fill%3D%22%23fff%22%3E%3Cpath%20d%3D%22M12.3%208.6a3.1%203.1%200%200%200%200%206.2z%22%2F%3E%3Crect%20x%3D%2212.05%22%20y%3D%228.6%22%20width%3D%221.7%22%20height%3D%2210.8%22%20rx%3D%22.2%22%2F%3E%3Crect%20x%3D%2214.85%22%20y%3D%228.6%22%20width%3D%221.7%22%20height%3D%2210.8%22%20rx%3D%22.2%22%2F%3E%3Crect%20x%3D%2212.05%22%20y%3D%228.6%22%20width%3D%224.5%22%20height%3D%221.7%22%20rx%3D%22.2%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E",
|
||||
"main": "index.js",
|
||||
"capabilities": [
|
||||
"sidebar-panel",
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 695 B After Width: | Height: | Size: 398 B |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 576 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 680 B |
|
|
@ -1,42 +1,27 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="Word editor">
|
||||
<title>Word editor</title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<!--
|
||||
Silent Mode's own mark for the Word editor.
|
||||
Silent Mode's mark for the Word editor.
|
||||
|
||||
It is deliberately NOT Microsoft's Word icon. That icon — and the blue
|
||||
sheet with a white "W" that goes with it — is Microsoft's trademark, and
|
||||
borrowing it to make our extension look official is exactly the kind of
|
||||
thing a browser that talks about sovereignty should not do.
|
||||
Deliberately not Microsoft's Word icon. That mark, and the blue sheet
|
||||
with a white "W" it belongs to, are Microsoft's trademarks; dressing our
|
||||
extension up as theirs would be both a legal problem and a dishonest one.
|
||||
|
||||
What it does instead is say "text document" in the only vocabulary that
|
||||
survives 16 pixels: a portrait page with a turned corner, a coloured
|
||||
heading rule, a few lines of body text, and a pilcrow — the paragraph
|
||||
mark every word processor has used since the trade was done on paper.
|
||||
The accent is Silent Mode's green, not Office blue.
|
||||
It shares pdf-editor's page silhouette, corner geometry and weight so the
|
||||
two editors read as a set in the dock, and carries a pilcrow — the
|
||||
paragraph mark every word processor has used since the trade was done on
|
||||
paper — rather than a format label. The glyph is drawn as paths, not
|
||||
<text>: an icon that needs a particular font installed to make sense is
|
||||
an icon that eventually renders wrong somewhere.
|
||||
-->
|
||||
<defs>
|
||||
<linearGradient id="dx-page" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#ffffff"/>
|
||||
<stop offset="1" stop-color="#e9edf4"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- the sheet, with the top-right corner turned down -->
|
||||
<path d="M6.5 2.5h12l7 7v20a1 1 0 0 1-1 1h-18a1 1 0 0 1-1-1v-26a1 1 0 0 1 1-1z"
|
||||
fill="url(#dx-page)" stroke="#2a3242" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
<path d="M18.5 2.5v7h7" fill="#cfd8e6" stroke="#2a3242" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
|
||||
<!-- heading rule, then body lines -->
|
||||
<rect x="9.5" y="13" width="9" height="2.2" rx="1.1" fill="#0ac18e"/>
|
||||
<rect x="9.5" y="17.6" width="13" height="1.6" rx=".8" fill="#97a3b6"/>
|
||||
<rect x="9.5" y="21" width="13" height="1.6" rx=".8" fill="#97a3b6"/>
|
||||
<rect x="9.5" y="24.4" width="8.5" height="1.6" rx=".8" fill="#97a3b6"/>
|
||||
|
||||
<!-- pilcrow, bottom-right, overlapping the page edge so it reads as a mark
|
||||
rather than as more body text -->
|
||||
<g transform="translate(20.4 19.2)">
|
||||
<circle cx="4.6" cy="4.6" r="5.4" fill="#0ac18e"/>
|
||||
<path d="M6.6 1.9H3.9a1.85 1.85 0 0 0 0 3.7h.8M5.3 1.9v5.6M6.6 1.9v5.6"
|
||||
fill="none" stroke="#0a0f16" stroke-width="1.15" stroke-linecap="round"/>
|
||||
<path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2z" fill="#2563eb"/>
|
||||
<path d="M14 2v6h6z" fill="#1e40af"/>
|
||||
<g fill="#fff">
|
||||
<!-- bowl: a half disc hanging off the left stem -->
|
||||
<path d="M12.3 8.6a3.1 3.1 0 0 0 0 6.2z"/>
|
||||
<!-- the two stems -->
|
||||
<rect x="12.05" y="8.6" width="1.7" height="10.8" rx=".2"/>
|
||||
<rect x="14.85" y="8.6" width="1.7" height="10.8" rx=".2"/>
|
||||
<!-- top bar tying the bowl and stems together -->
|
||||
<rect x="12.05" y="8.6" width="4.5" height="1.7" rx=".2"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 1.3 KiB |
87
docs/DESIGN-one-extension-catalogue.md
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# One catalogue, one set of rules
|
||||
|
||||
Status: proposed, 2026-09-22. Not implemented.
|
||||
|
||||
Theseus currently runs two extension systems that happen to produce the same
|
||||
kind of thing.
|
||||
|
||||
| | Bundled add-ons | Community extensions |
|
||||
|----------------|-------------------------------------|---------------------------------------|
|
||||
| How it arrives | compiled into the build, seeded into the profile on first run | installed from the gateway catalogue |
|
||||
| Who signs | the Silent Mode operator's Ed25519 key, pubkeys compiled into the app | the owner of a BNS name, checked against the chain |
|
||||
| Update channel | `theseus.x/extensions/<id>/updates.json` | `theseus.x/extensions/community/<id>/updates.json` |
|
||||
| In Settings | a section headed "Built into Theseus" | a separate section headed "Community" |
|
||||
| How to publish | `scripts/sign-addon-update.mjs` + `sia-upload` | the publish page, or `PUT /api/ext/…` |
|
||||
|
||||
Two catalogues in the UI, two trust rules, two publishing paths — for a
|
||||
difference that is really only "who wrote it" and "is it there on day one".
|
||||
|
||||
## What this should be instead
|
||||
|
||||
**One catalogue.** Every extension is listed in the same place, whoever wrote
|
||||
it. Silent Mode publishes under a Silent Mode name the same way anyone else
|
||||
publishes under theirs. Being first-party is a fact about the publisher, not
|
||||
a separate distribution system, and the UI should say so with a line of text
|
||||
rather than a second list.
|
||||
|
||||
**One rule for trust.** An extension is installable because a signature
|
||||
checks out against the current owner of the publisher's name. That is
|
||||
already what `verifyPublisherEntry` does; the operator Ed25519 key becomes
|
||||
either one more accepted signer or nothing at all. `pickChannelEntry`
|
||||
already accepts both, so this is mostly deletion.
|
||||
|
||||
**Defaults are configuration.** Which extensions a fresh Theseus starts with
|
||||
is a list of ids in the app's config, not a folder that gets copied. Changing
|
||||
the default set should be editing that list, not moving directories around
|
||||
and rebuilding.
|
||||
|
||||
## The one thing that needs care
|
||||
|
||||
If defaults are fetched from the catalogue on first run, a fresh install with
|
||||
no network — or with the gateway down — has no editor, no wallet and no
|
||||
screenshot tool. That is a real regression from today, where the build
|
||||
carries them.
|
||||
|
||||
So keep shipping the code, but stop treating it as a different kind of thing:
|
||||
|
||||
- The build continues to include the default extensions' folders. They are a
|
||||
**pre-seeded cache**, not a separate class of add-on.
|
||||
- On first run they are installed from that cache, then verified and updated
|
||||
against the catalogue exactly like anything the user installed by hand.
|
||||
- If a seeded copy fails verification, it is quarantined rather than run —
|
||||
a cache is only a cache.
|
||||
|
||||
That keeps offline first-run working while leaving one set of rules
|
||||
everywhere else.
|
||||
|
||||
## Sketch of the work
|
||||
|
||||
- **Config**: `defaultExtensions: [{ id, publisher }]` in the app's settings,
|
||||
read at first run and on profile migration. Ids the user has removed are
|
||||
remembered and not re-seeded — uninstalling a default must stick.
|
||||
- **`main.js`**: seeding installs *through* the normal install path instead
|
||||
of copying a folder and calling it built-in. One code path, one place where
|
||||
a signature is checked.
|
||||
- **`settings.html`**: one list. "Installed by default" becomes a badge on a
|
||||
row, next to the publisher's name. The separate Community section and its
|
||||
Install button fold into the single catalogue view.
|
||||
- **`addon-updater.js`**: drop the distinction between the two channel
|
||||
shapes; an extension has a publisher and an updates URL.
|
||||
- **Gateway**: the catalogue gains first-party entries. Nothing changes
|
||||
structurally — `/api/ext` already stores whatever a name owner signs.
|
||||
- **Publishing**: `scripts/sign-addon-update.mjs` and the operator key stop
|
||||
being the first-party route. Silent Mode's own extensions go through
|
||||
`PUT /api/ext/<our-name>/<id>/<version>` like everyone else's, which means
|
||||
the release process needs that name's wallet rather than a key file.
|
||||
|
||||
## Open questions
|
||||
|
||||
- **Which name publishes Silent Mode's extensions?** Whoever goes first owns
|
||||
each id permanently. Worth deciding once, deliberately.
|
||||
- **Pinning.** A default extension is installed without anyone choosing it,
|
||||
so a compromised or coerced catalogue entry reaches every fresh profile.
|
||||
Pinning the default set to a version and a publisher in the app's config
|
||||
bounds that.
|
||||
- **Does the operator key survive at all?** Keeping it as an accepted signer
|
||||
for the default set is a hedge against the name-owner key being lost;
|
||||
keeping it forever means the two-rules problem never fully goes away.
|
||||