# Changelog

All notable changes to `@sqwig/core`, `@sqwig/react`, `@sqwig/vue` and
`@sqwig/angular`. The packages are released together and share a version number.

> **This file is the source of truth.** The repo is private, so customers get it
> in the no-npm dist zip (`sqwig.com/downloads/sqwig-core-<version>.zip`).

Sqwig follows [semantic versioning](https://semver.org). Breaking changes to the
public API require a major version.

## How versions relate to your license

A license key is **term-bound**: it carries a validity window and is checked
against the current date, entirely offline. While it is valid it applies to
whatever version you run. Past its end date the key stops verifying and the
component returns to the Free tier — on whatever build you have installed.
**The core editor keeps working and your content is untouched**; the runtime
does not throw. What changes is entitlement: the badge returns, and features
designated as paid revert to their Free-tier behaviour.

---

## 1.10.4

Bug fix in paste and save.

### Fixed

- A Word paste could leave a stray line break in `getHTML()` output. Browsers
  collapsed it on screen, so only the saved payload carried it.

---

## 1.10.3

Documentation only.

### Added

- A link in each package README to the 30-day trial of the paid features.
  One email, no card.

### Changed

- Each package README now states the Free tier's scope: development, evaluation
  and non-commercial production. No license change.

---

## 1.10.2

Documentation only.

### Fixed

- Links in each package README now read as titles rather than showing the full
  web address.

---

## 1.10.1

Documentation only.

### Changed

- Updated the sqwig.com links in each package README.

---

## 1.10.0

Export to PDF.

### Added

- **Export to PDF** (paid plans). A toolbar button and `editor.exportPdf()`,
  paginated and written entirely in the browser. The text is real text:
  selectable, searchable, and readable by the resume parsers an applicant
  tracking system runs. The file carries a structure tree, so a screen reader
  gets headings, lists and tables rather than one flat block.
- Both exports take an options object: `pageSize` ("letter" or "a4") and
  `pageNumbers` (on by default).
- `baseUrl` resolves relative links, in both formats. Absolute links are never
  touched.
- `fonts` embeds a TrueType font you supply, for characters the built-in fonts
  cannot draw. We subset it to what the document uses.
- `onExportReport` reports what an export could not do: characters with no
  glyph, right-to-left text, unresolved links, per-image outcomes.
- `tel:` and `sms:` links are now kept.

### Changed

- The page-number footer in Word exports can be turned off.
- A Word export with only a width or only a height on an image now keeps the
  image's shape instead of stretching it.

### Fixed

- Text color and highlight now work on the Free tier as intended.

### Compatibility

Backward compatible with 1.9.0. Every export option is optional and existing
calls are unchanged. `getHTML()` gains one thing: `tel:` and `sms:` hrefs are
now kept rather than stripped, which widens the output whitelist.

## 1.9.0

Text color and highlight are now free, on every tier.

### Changed

- Text color and highlight available in all tiers.

### Compatibility

Backward compatible with 1.8.1. Free-tier editors gain two toolbar controls;
no API or `getHTML()` change.

## 1.8.1

Sharper menus, and each formatting button drawn in the style it applies.

### Compatibility

Backward compatible with 1.8.0. No API changes and `getHTML()` output is
unchanged for every input.

## 1.8.0

Upgraded toolbar icons & keyboard shortcuts.

### Added

- Keyboard shortcuts: <kbd>Ctrl/Cmd+M</kbd> and <kbd>Ctrl/Cmd+Shift+M</kbd> for
  indent, <kbd>Shift+F3</kbd> to cycle case, <kbd>Ctrl/Cmd+,</kbd> and
  <kbd>Ctrl/Cmd+.</kbd> for subscript and superscript, and
  <kbd>Ctrl/Cmd+\</kbd> to clear formatting. Sqwig does not take keys your
  browser already owns, so <kbd>Ctrl/Cmd+R</kbd> still reloads. All tiers. The
  full list is at <https://sqwig.com/docs#shortcuts>, which is new.

### Changed

- Toolbar icons redrawn — noticeably sharper at 100% browser zoom.

### Compatibility

Backward compatible with 1.7.1. No API changes and `getHTML()` output is
unchanged for every input.

## 1.7.1

Undo now fires one `change` event, not two. Smaller bundle.

### Fixed

- Undo and redo emitted the `change` event twice and refreshed the toolbar
  twice. They now fire once each. If your app re-renders, saves, or sets a
  dirty flag on `change`, it was doing that work twice for every undo.

### Changed

- The bundle is about 20KB smaller.

### Compatibility

Backward compatible with 1.7.0. No API changes, `getHTML()` output is unchanged
for every input, and the word and character counts are unchanged for every
input.

## 1.7.0

Set the editor's height, or let people drag it. Ships on every tier.

### Added

- `maxHeight` caps the editor's height. Past the cap the text scrolls inside
  the box instead of growing and pushing your page down.
- `resizable: true` adds a drag handle on the bottom edge, worked by pointer
  or arrow keys. Desktop only; `maxHeight` applies everywhere.
- `height` sets a starting height, `onResize` reports a new one. Sqwig stores
  nothing on the device: persist it yourself if you want it back.

### Compatibility

Backward compatible with 1.6.1. Every new option is off by default, so an
editor that sets none of them renders exactly as before. `getHTML()` output is
unchanged for every input.

## 1.6.1

Bug fixes and improved Undo performance.

### Compatibility

Backward compatible with 1.6.0. No API changes; `getHTML()` output is unchanged
for every input.

## 1.6.0

Undo, rebuilt. Ships on every tier.

### Fixed

- Undo restores the document exactly for every command, in every position. This
  clears the limitation noted in 1.5.1.
- Accepting a spelling suggestion is one undo step.
- Redo replays every command exactly.

### Changed

- **Typing undo is word-burst, not per-character.** One Ctrl+Z takes back a run
  of typing, the way Word does. A pause, a caret move, Enter, a paste or any
  toolbar command starts a new step.
- Undo and Redo grey out when there is nothing to undo or redo.

### Compatibility

Backward compatible with 1.5.1. No API changes; `getHTML()` output is unchanged
for every input.

## 1.5.1

Undo fixes. All ship on every tier.

### Fixed

- Fixed undo across all toolbar commands — the document now restores exactly.
- Lists no longer lose text when applied to a document's leading heading, or to
  a quoted paragraph.
- Word count no longer merges words across block boundaries.

### Changed

- `getText()` now separates blocks: a blank line between blocks, a newline
  between list items and table rows, a tab between table cells.

### Known limitation

- When a non-paragraph block has no paragraph above it — a quote or list at the
  very top of a document — blockquote, code block, alignment, indent and lists
  apply but Ctrl+Z steps past them. Content is not altered.

### Compatibility

Backward compatible with 1.5.0 except `getText()` output, which now carries
block separators. No other API changes; `getHTML()` output is unchanged for
every input.

## 1.5.0

All six additions ship on every tier.

### Added

- **Find & replace**: toolbar button or Ctrl+F; Ctrl+H opens the Replace
  row. Live match counter, match case, wraparound; searches code blocks.
  Replace All is one undo step.
- **Images**: insert by URL, or set an `imageUpload` handler — it receives
  the file's bytes and returns a URL, and is used for file picks and images
  pasted from Word. Click an image to edit alt text or delete it. Failed
  uploads keep a placeholder with Retry and Remove. Sqwig never hosts or
  embeds image data: `src` is always an http(s) URL your app supplied, and
  `getHTML()` never contains a placeholder.
- **Markdown**: opt-in `pasteMarkdown` converts plain-text markdown on
  paste (off by default), and `getMarkdown()` serializes the document.
- **Fullscreen**: a toolbar toggle; Escape exits.
- **Horizontal rule**: a toolbar button, or `---` and Enter. Exports as
  Word's own rule paragraph.
- **List numbering**: Restart at 1, Continue numbering and Set numbering
  value on the numbered-list menu — the standard `start` attribute,
  honored by Export to Word.

### Fixed

- **Code block**: toggling one on fused a paragraph's line breaks and
  could skip the undo entry. Line structure is now preserved and toggling
  either way is one undo step.

### Changed

- `getHTML()` may now emit `hr`, `ol` with `start`, and `img` with
  `src`/`alt`/`width`/`height`. Documents using none of the new features
  serialize exactly as before.
- With an `imageUpload` handler configured, images pasted from Word upload
  through it instead of dropping to the `[image]` placeholder.

### Compatibility

- Fully backward compatible with 1.4.0. `pasteMarkdown` and `imageUpload`
  default to off/absent; without them, paste behaves byte-for-byte as
  before.

## 1.4.0

### Added

- **Text color and highlight** (paid plans): two Word-style split buttons in
  the toolbar. Font color offers Automatic, a color grid, recent colors and
  a hex field; the highlighter offers Word's fifteen highlighter colors, and
  **Export to Word maps every one to the same-named Word highlighter color**.
  Colors you have applied stay in your content on every tier and after
  license expiry — only the controls follow the plan.
- **Toolbar configuration** (all tiers): the `toolbar` option now also
  accepts a preset name or an ordered list of button ids —
  `toolbar: ["bold", "italic", "|", "link"]` — plus a `{ mode, items }`
  form. `TOOLBAR_PRESETS.full` and `.minimal` are exported for composing.
  Unknown ids are ignored with a single console note.
- **`@sqwig/angular`**: an official Angular wrapper (Angular 15 or newer,
  standalone component). One `options` input, three outputs, and an
  `editor` property that exposes the full core editor — the same thin,
  uncontrolled design as `@sqwig/react` and `@sqwig/vue`. Integration
  guide at sqwig.com/docs/angular.

### Changed

- `getHTML()` may now emit `background-color` on spans where the new
  highlight feature is used — a widening of the output whitelist. Documents
  that do not use highlight serialize exactly as before.

### Compatibility

- Fully backward compatible with 1.3.2. Every existing `toolbar` value keeps
  its meaning; the default toolbar is unchanged. Free tier: the editor,
  spellcheck, paste and toolbar configuration are all available; the color
  controls do not appear, and colored content pasted or set via `setHTML`
  still renders and exports.

## 1.3.2

### Fixed

- Table header rows now keep their header styling when exported to Word, and
  repeat across page breaks.

### Changed

- **Header tinted** now marks the first row as a real header row.

### Compatibility

Fully backward compatible with 1.3.1. No API changes. Applying **Header
tinted** now also marks the first row as a header, so the document gains real
`<th>` cells.

## 1.3.1

### Changed

- **Chrome polish across the editor.** The suggestion popover, the list and
  table panels, and link editing now share one icon language and one way of
  marking the current selection.

### Fixed

- Assorted visual and accessibility refinements in menus, panels and
  popovers.

### Compatibility

Fully backward compatible with 1.3.0. Presentation only: no API changes, and
`getHTML()` output is unchanged for every input. Free on every tier.

## 1.3.0

### Added

- **Table authoring** (all tiers): size-picker insert, row/column
  inserts and deletes, Tab between cells, header rows (real `<th>`),
  and a contextual balloon when the caret is in a table. Every
  operation is one undo step.
- **Table styles** (paid plans): Banded rows and Header tinted presets
  as a single `data-sqwig-style` attribute; sort rows; convert
  tab-separated text to a table and back.
- **Export to Word**: header rows and presets map to real Word table
  styles.

### Changed

- Paste keeps declared `<th>` header cells (previously folded to
  `<td>`) and squares up tables left ragged by dropped merged cells.

### Compatibility

- Fully backward compatible with 1.2.0. New markup (`<th>`,
  `data-sqwig-style`) appears only where the features are used.
  Applied styles survive license expiry — only the controls follow
  the plan.

## 1.2.0

### Added

- **Custom dictionaries** (paid plans): the new `customWords` option ships
  your application's own vocabulary — product names, jargon, acronyms — so
  those words are correct for every user, on every device, with no per-user
  setup. The words are configuration, never written to storage; each user's
  own "Add to dictionary" words layer on top. Plans include a word
  capacity — documented with the option at sqwig.com/docs. Entries beyond
  the capacity stay inactive, with a single console note; nothing throws.
- **`@sqwig/vue`**: an official Vue 3 wrapper. One `:options` prop, three
  emits, and a template ref that exposes the full core editor — the same
  thin, uncontrolled design as `@sqwig/react`. Integration guide at
  sqwig.com/docs/vue.

### Compatibility

- Fully backward compatible with 1.1.0 — the new option defaults to
  existing behaviour, and emitted HTML is unchanged: `customWords` affects
  spellcheck squiggles only, never what `getHTML()` produces.
- Free tier: the editor, spellcheck and paste are untouched. Passing
  `customWords` without a paid plan leaves it inactive, with one console
  note saying so.

## 1.1.0

### Added

- **Export to Word** (paid plans): one click downloads the document as a
  real `.docx` — assembled entirely in your user's browser, nothing leaves
  the machine. Real Word headings, lists, hyperlinks and tables; also
  available programmatically as `editor.exportDocx()`.
- **`pasteFontSizes` option** (all tiers, default off): recover declared
  font sizes from rich pastes — `"relative"` preserves the document's own
  size hierarchy, `"absolute"` keeps literal point sizes.
- **`pasteColors` option** (all tiers, default off): recover declared text
  colour from rich pastes as clean `#rrggbb`.

### Changed

- **Free tier**: hiding the trust footer — `trustIndicator: false`, or
  blanking its label with `text: ""` — is now honoured on paid plans only;
  Free-tier editors keep the footer and default label (one console note
  says so). Rewording the label with your own `text` still works on every
  tier. Nothing else changes on Free — the editor, spellcheck and paste
  are untouched.

### Fixed

- Fixed an inline formatting bug.

### Compatibility

- Fully backward compatible with 1.0.2 — all new options default to
  existing behaviour, and emitted HTML is unchanged unless you opt into
  the new paste options.

## 1.0.2 — 2026-07-31

### Fixed

- **Spellcheck no longer flags possessive forms** (`device's`, `computer's`)
  as misspellings. A real typo like `devcie's` still squiggles. Contractions
  (`don't`, `it's`) are unaffected.

### Compatibility

- Fully backward compatible with 1.0.1 — no changes to the API, options, or
  emitted HTML output.

## 1.0.1 — 2026-07-31

### Fixed

- **Atomic undo/redo:** every toolbar action, formatting behaviour, and
  accepted spelling suggestion is now a single, predictable undo step —
  Ctrl+Z reverses it, Ctrl+Y replays it. Previously, some actions could be
  skipped by undo.
- **Code-block history:** resolved an issue where code-block actions could
  fragment the undo history.
- **Release verification:** an automated test matrix now drives every editor
  command in a real browser and asserts undo/redo integrity, as a continuous
  release gate.

### Compatibility

- Fully backward compatible with 1.0.0 — no changes to the API, options, or
  emitted HTML output. One narrow behaviour is unchanged rather than improved:
  structural changes to a list or code block that is the document's first
  block apply correctly but are not recorded as undo steps.

## 1.0.0 — 2026-07-29

**First public release of Sqwig v1.0.**

**Why 1.0 and not 0.x:** the public API is frozen. `originMatches`, the bundled
public-key trust set, and the required `license` option all bake into installed
bundles and cannot be corrected remotely, so they were settled before publishing
rather than after. A `0.x` version would also have broken `^` ranges for
consumers — under semver, `^0.1.0` refuses minor updates — which contradicts the
license promise that released versions keep working.

### The editor

- Word-tier formatting toolbar, always active: undo/redo, headings, font family
  and size, bold/italic/underline/strikethrough, sub/superscript, change case,
  clear formatting, alignment, line spacing, lists, indent, and links.
- **Developer bundle**: inline code, plain code blocks that **spellcheck skips**
  (`useEffect` isn't a typo), blockquote, `~~strike~~` autoformat, and an
  optional desktop floating toolbar (`toolbar: "floating"`; touch always gets the
  fixed bar).
- **Clean paste** from Word and Google Docs: bold, italic, lists, links,
  paragraphs and h1–h3 survive; fonts, colours and vendor markup are stripped.
  Tables render and their cells are editable; images become a placeholder. Output
  is always clean, semantic HTML.
- Read-only mode, `maxLength`, autolink, paste-as-plain-text.
- Light and dark presets, every colour themable through `--sqwig-*` custom
  properties.

### Spellcheck

- Runs **entirely on the device**. A WASM engine plus a split dictionary is
  fetched as static assets; **no text is ever transmitted**, and there are zero
  runtime calls to Sqwig servers.
- Suggestions at the word, ignore-once, and add-to-dictionary that persists.
- The engine runs in a **Web Worker**, so loading it never blocks the host page.
- If the dictionary fails to load, the component still works as a plain rich text
  box. Spellcheck is never load-bearing.

### Mobile

iOS Safari and Android Chrome are supported targets, verified on real hardware.
Native autocorrect is suppressed so it cannot silently rewrite words under
Sqwig's own underlines, popovers avoid the on-screen keyboard by measuring the
visual viewport, and touch targets are at least 40px.

### Licensing

- Three states: no `license` value → a fully working editor with a
  "Powered by Sqwig · unlicensed" badge; `license: "free"` → the Free tier with a
  clean badge; a valid key → per entitlements.
- **Licensing does not gate the editor.** Verification is fail-open: any failure
  — invalid, malformed or expired — degrades to the Free tier with the badge and
  a single console note. The runtime does not throw over licensing.
- Keys are **offline Ed25519 tokens** — signature, licensed domains and validity
  window are all checked on the device. No license server, no phone-home, no
  usage metering, and no per-developer fees.
- **Keys are term-bound.** A key carries a validity window checked against the
  current date. While valid it applies to whatever version you run; after it ends
  the component returns to the Free tier with the badge, on whatever build is
  installed. An expiry changes an entitlement, not how the editor works.
- **Keys match on hostname.** Scheme, port and a leading `www.` are ignored, so
  `https://acme.com`, `http://acme.com:8443` and `www.acme.com` are one license.
  Subdomains are distinct; use `*.acme.com` to cover them.
- **Development and internal hosts need no key**: `localhost`, `*.localhost`,
  reserved TLDs (`.test`, `.local`, `.internal`, …), private IP ranges, and
  single-label hostnames, on any port.
- **Packaged application shells are not a licensed environment** — Capacitor,
  Cordova, Ionic and Electron builds, and browser extensions, run on the Free
  tier with the badge, and a paid key will not remove it there. Contact
  hello@sqwig.com if you need this.
- **One license per application**, stackable. Staging and preview environments of
  a licensed application are not additional applications.
- **Annual, single payment, no auto-renewal**, and all sales are final — the Free
  tier is the evaluation. Renewal is an explicit re-purchase.

### Distribution

- `@sqwig/core` (framework-agnostic) and `@sqwig/react` (thin wrapper).
- A no-npm zip of the same files for wwwroot-style stacks:
  `sqwig.com/downloads/sqwig-core-<version>.zip`.
- Runnable React and Angular examples; ASP.NET Core and Blazor integration guides.

### Security

The HTML Sqwig emits is produced by a whitelist serializer, and untrusted HTML is
parsed in an inert document so nothing executes during sanitisation. The output
contract and the trust boundary are documented at
[sqwig.com/docs#security](https://sqwig.com/docs#security); to report a
vulnerability, email security@sqwig.com.

### Disclaimers

- **No warranty.** The software is provided "as is", to the extent permitted by
  law. `LICENSE.txt` in each package and the [Terms of
  Service](https://sqwig.com/terms) are the governing documents; everything above
  is a description of the release, not a term of the license.
- **Spellcheck is an automated aid.** It will miss misspellings and flag correct
  words, including names and domain vocabulary. You are responsible for your
  application's content.
- **Sanitisation is not a substitute for your own boundary.** Sqwig emits a
  whitelisted semantic subset, but client-side filtering should not be anyone's
  only defence, ours included: re-sanitise server-side before stored HTML is
  rendered again, and set a strict CSP.
- **Third-party notices** for bundled components ship in
  `THIRD_PARTY_LICENSES.txt`, with the dictionary's SCOWL notice at
  `dist/dict/LICENSE.SCOWL`.
