PlanCake 1.0: read and annotate Markdown with a screen reader - #1
Merged
Merged
Conversation
Lock navigation to in-page anchors and block script through a CSP, split interface and document language (View submenus plus a default in Settings), strip multi-line note prefixes on read, refuse to write non-UTF-8 files, make CLI output UTF-8 with redirect-aware console attach, add --single-token, define anchor edge cases, and close the hidden setting dependencies between tasks.
Add opening from the clipboard and from a link (new Task 11, later tasks renumbered), notes JSON export, a note style setting (role=note by default, or button), an Edit menu, an Advanced tab for converting non-UTF-8 files, keyboard-and-mouse and LTR/RTL as a rule for every UI task, and repository metadata. Drop the > prefix from written notes, confirm plancake.oire.dev, and give the key generation commands in the updates task.
Take the ANSI fallback from Windows (injectable for tests), defer dialogs and menus started from WebView2 events with BeginInvoke, keep full block text for position restore and copy, route every open through one OpenFile, pass localized strings into the renderer, fix console encoding when redirected, harden clipboard and link opening, complete the deferred menu items, and repair the Notika path.
Rename the project, test project, solution and namespaces from WinFormsTemplate to PlanCake. The executable is plancake.exe, App.Name is PlanCake (data folder, PlanCake.cfg, PlanCake.po/.mo), and the unused database constants are gone. Get-CatalogName.ps1 now reads App.Name from App.cs, the same constant the app loads its catalog by, instead of the AssemblyName. Replace the template README with a PlanCake one, rewrite the CLAUDE.md title and structure section, and add the Apache 2.0 LICENSE.
Add the WebView2 host (DocumentView) with its user data folder under the app data folder, the web folder served from https://app.plancake/, the browser's own menus, accelerators and status bar off, and JSON messages both ways. Program checks for the WebView2 Runtime before creating the window and offers the download page when it is missing. Host shortcuts go through one table (HostCommands). Keys pressed in the document never reach ProcessCmdKey: the WinForms control raises its own KeyDown from the browser's AcceleratorKeyPressed, so DocumentView passes those on and MainWindow handles both paths the same way. StatusAnnouncer speaks status messages through a UI Automation notification. TextDirection and DialogHelper come from SIC. spike.html is the throw-away page for the JAWS checklist; F9 focuses its third paragraph. Directory.Build.targets drops the package's WPF reference, whose WindowsBase clashes with WinForms (MSB3277).
docs/jaws-spike.md records each checklist answer and the decision taken. Plan changes: Enter and a mouse click cannot be told apart, so both activate a block or note and double-click does nothing extra. Blocks and notes lose their permanent tabindex, which put JAWS in forms mode on Enter; the page adds it only while moving focus, and a keydown Enter on a block is handled like a click. JAWS takes F8 for extended select, so note navigation moves to F9 / Shift+F9. The announce page message is dropped, since UI Automation notifications are heard in the document. New Task 7a makes task-list checkboxes toggleable, behind a ConfirmTaskToggle setting. The shortcut table follows: notes on F9 / Shift+F9, the spike's focus command on F12.
Add NoteMarkers (default [usernote]/[/usernote], single-token mode when the closing marker is empty, Validate returning an error code for the caller to localize), the Note record and NoteParser. Parse returns the notes with 1-based lines and character spans, and the source with the notes stripped line by line plus a map from stripped to original lines. Unterminated notes run to the end of the file and are flagged; continuation lines lose the indentation PlanCake writes.
Add Markdig and a MarkdownRenderer that renders the note-stripped source, stamps every annotatable block (paragraph, heading, list item, code block, table row) with its original 1-based line range and dir="auto", and collects a BlockInfo per block with its plain text and an 80-character excerpt. Notes are anchored to their block and inserted after it: as user notes or buttons in interactive mode, as static role="note" elements in a standalone export document carrying a script-free content security policy. Localized labels come in through RenderStrings, so the renderer never touches the catalog.
Add MarkdownFile, which detects a file's encoding (UTF-8 or UTF-16 by BOM, else strict UTF-8), BOM and dominant line ending and writes it back the same way, atomically through a temporary file, retrying a locked file for about a second. A file that is not valid in its encoding is decoded with the ANSI code page and opened read-only, or converted once to UTF-8 without BOM when ConvertToUtf8 is on. Add NoteStore: add, edit, delete and clear notes, each refusing with StaleFileException when the file differs from the rendered text. New notes go after the block's last line and after the notes already there, indented to a list item's content column, never with a quote marker. Note text is validated against the markers, and every change can be undone and redone unless the file changed in between.
Replace the JAWS spike page with the real document page: index.html with a strict content security policy, app.js implementing the page protocol (render, strings, focus and note navigation; activate, context menu, position, links and dropped files back to the host) without permanent tabindex so Enter does not switch JAWS to forms mode, and app.css with focus, note, high contrast and dark mode styles. MainWindow.OpenFile reads a file with MarkdownFile, renders it, sets the window title, announces a read-only or converted file and an unterminated note, and keeps the reading position across re-renders through PositionRestorer. Links go through LinkResolver; every navigation after the page load is canceled. Files open from the command line and by drag and drop; Ctrl+Plus, Ctrl+Minus and Ctrl+0 zoom in 10% steps.
The window keeps a browser-like history of visited files. Opening another file by any route records the file being left with its reading position and clears the forward list; Alt+Left and Backspace go back to the previous file at the same block, Alt+Right goes forward. The ends and files that no longer exist are announced, and missing files are dropped from the history. Backspace never fires from a text box, and the page reports it as goBack when the browser does not pass it to the host. The plan records the Task 6 JAWS results, the new keys and View menu items, the Task 7 check for the history, and how Task 10's one window per file treats a history entry open in another window.
Enter or a click on a block opens the new note dialog and writes the note after the block; Enter on a note edits it. The Applications key, Shift+F10 and a right-click open a native context menu at the element (Add note, Edit note, Delete note, Copy block text). Deleting asks first. Ctrl+Z and Ctrl+Y undo and redo note changes. Each change is announced and the view returns to the note. NoteActionRunner turns every store outcome into what the user is told: a file changed on disk is shown again and the typed text kept for the retry, a write failure reopens the dialog with the text, and a read-only file is refused with the reason. The page now sends its devicePixelRatio with the context menu rectangle.
The Task 7 screen reader check found Markdown in a note shown raw, with backticks read out. A user note and an exported note now render their text as Markdown with the document's extensions: every inner block gets dir="auto" and no data-lines, a heading becomes a bold paragraph so it stays out of heading navigation, typed line breaks are kept, raw HTML is shown as text and footnotes are left out. A note shown as a button holds phrasing content only: inline Markdown, links as their text. The delete confirmation shows the note as plain text. The file keeps exactly what the user typed.
Every note is now a role="note" user note. Long button labels are hard to listen to, and the note element already works with Enter and with F9 / Shift+F9, so the Button style is gone: the NoteStyle enum, the button rendering path with its inline-only Markdown helper, the "Note:" label in RenderStrings, and the button-only rules in app.css. The plan and the JAWS spike notes record the decision, and the Task 7 screen reader results now include Back and Forward.
Task-list check boxes in the document are now enabled: Space, Enter or a click toggles one, and the page sends the item's lines and new state to the host. After a confirmation that says the file on disk will be changed (on by default until the setting arrives), the host rewrites that item's [ ] or [x] and nothing else, through the same stale-safe write and undo history as notes. The view re-renders with the focus on the same check box and announces "Task checked" or "Task unchecked". Cancelling, a read-only file or a failed write sets the check box back to the file's state. Exported HTML keeps the check boxes disabled. TaskToggle holds the pure marker rewrite; Undo and Redo announce toggles like note actions.
An unchecked task item with some, but not all, of its nested task items checked (at any depth) now shows its check box as partially checked. The renderer marks it data-mixed and the page sets indeterminate after every render and every reverted toggle. A checked parent shows checked whatever its children say. Nothing new is written to the file: toggling such a parent writes [x] on its own line only. Exported HTML keeps disabled check boxes and marks a partially checked one aria-checked="mixed", since no script runs there.
Following a relative .md link landed at the end of the new file. A file opened fresh had no saved position, so the page was sent no focus and only scrolled to the top; the followed link went away with the old content and JAWS kept its old place in the virtual buffer, which in a shorter file is the end. A document just opened now puts the focus on its first block, unless Back or Forward brings a saved position for it. A re-render of the same file still returns to where the user was. The plan records the Task 7a screen reader results and adds the partially checked and link checks to Task 8's list.
- SplitContainer with the document and a NativeListView of the notes (Lines, Block, Note as plain text), labeled "Notes", refilled after every render; unchanged rows are left alone and the selection stays on the same note (PositionRestorer.FindNote) - F6 switches between the document and the list; View > Notes list shows or hides it (first NativeMenuBar, View menu only) - Enter in the list jumps to the note, Delete deletes it, context menu with Edit note and Delete note; actions started in the list keep the focus there - F9 / Shift+F9 move the list selection when the list has focus; the page tracks focus reaching links and check boxes as the current block
- The list view's system proxy ignores the window text AccessibleName
sets, so JAWS heard no name: the list window is also named through
MSAA annotation (WindowAccessibleName); AccessibleName is _("Notes")
and the visible label stays. CLAUDE.md records the exception.
- The notes list selects the note the document reports (activateNote,
context menu, position, F9, a note added or edited there) without
moving the focus (PageMessages.FindNote).
- A different file gets a freshly loaded page and is rendered on its
ready message, so JAWS starts it at the top instead of keeping its
old buffer offset; FocusDocument no longer refocuses a document that
already has the focus. Page messages and renders are logged at debug.
The notes list, F6, F9, the list menu and hiding the list passed; the list name, following the document and the link landing at the top pass on re-check.
The window gets its full menu bar: File, Edit, View, Notes and Help, built from the host command table so every shortcut shown is the one that runs, from the document too. Items that need a file or a note are disabled without one, and separators left with nothing to separate are dropped. View offers the interface language (saved and applied live) and the document language (this document only). File opens files and the editor, View reloads, Edit deletes all notes after asking. Help shows every shortcut in one list, and the About dialog. Tab and Shift+Tab now leave the notes list for the document, so Tab cycles through both panes in either direction.
A FileWatcher watches the open file's folder, debounces events by 300 ms and ignores text PlanCake itself shows or wrote. An outside change reloads the view at the same block and announces it (the ask first branch is built, AutoReload hard-coded until the settings task). A deleted or renamed file disables note commands, Reload and Open in editor until it comes back, then reloads. SingleInstance claims each open file through a named pipe named after the SHA-256 of its normalized path. Starting PlanCake on a file that is already open, or opening it from a link, the history or File > Open, activates the window showing it instead.
An outside edit sent the JAWS virtual cursor to the top: every reload was posted with focus on the block the file opened at, because the page never sees the virtual cursor move, and replacing the page content destroyed the nodes the cursor was on. Renders the user did not ask for by acting on a block now carry no focus, and every re-render of the same file updates the page in place (web/morph.js), keeping unchanged nodes. Its matching logic is tested through Jint. A Yes/No message box has no cancel, so Escape did nothing in the confirmations. Every yes-or-no question now goes through DialogHelper.Confirm, a task dialog where Escape and the close button answer No.
Outside edits keep the reading position and confirmations close with Escape on re-check; the other Task 10 and deferred Task 9 checks passed in the first round.
File > Open from clipboard (Ctrl+V) opens the first Markdown file among files copied in Explorer, a path copied as text (quotes from "Copy as path" stripped) or an http(s) link; anything else is announced. The decision is a pure ClipboardClassifier, tested without a clipboard. File > Open from link (Ctrl+L) asks for a link in a small dialog, pre-filled from the clipboard. MarkdownDownloader turns a GitHub blob link into its raw form, downloads with a 30-second timeout and a 10 MB limit, refuses error statuses and web pages, and saves the file to the Downloads folder, numbering the name when it is taken. The window then opens that local copy and announces where it was saved.
The start of a download announced the file name guessed from the link,
so a link to a web page was announced as "Downloading Oire.md" before
being refused. It now names the host ("Downloading from github.com...");
the file name comes only with "Downloaded and saved to", once the
content is accepted. Task 11's JAWS checks are recorded in the plan.
File > Settings (Ctrl+comma) opens a dialog with General, Notes and Advanced tabs. Config gains the [General], [Notes] and [Advanced] sections of the plan and drops ConfirmExit; each value is read on its own, and an unreadable enum, unusable markers or an unknown language falls back to its default alone. Every hard-coded default of earlier tasks now follows the setting and applies on OK without a restart: interface and document language, note markers (re-render), confirmations, the external change action, the notes list, Enter on a block (the page now sends the block's rectangle with activate, for its context menu), Enter in the note dialog, and conversion to UTF-8 (a read-only file open at the time is reopened).
On a machine whose ANSI code page is UTF-8 (65001), a Windows-1251 file was decoded "as ANSI" into replacement characters, and Convert to UTF-8 wrote them over the file. A file that is not UTF-8 is now decoded with the first legacy encoding that decodes it cleanly: the UTF.Unknown charset detector when it is confident on enough text, then the code page of the document language, then the ANSI code page unless it is 65001. When none fits, the file is read-only, never converted and never written, and the announcement says the encoding was not recognized. View > Document language reads such a read-only file again. File > Settings reads the settings file again before showing the dialog, so a setting saved in another window is shown, kept and applied. The Closing marker box gets an always visible hint, also read as its description.
The separate hint under the Closing marker box, also given as the box's description, was not read by the screen reader. The explanation is now part of the box's own label: "Closing marker (leave empty for a single marker that runs to the end of the line):". The refusal reason works as before. The plan records the Task 12 check results.
App.UpdatePublicKey now holds the Ed25519 public key of the pair in keys/ (gitignored), so update checks run and verify the appcast.
All four checks pass: the update settings read with their labels and defaults, Help > Check for updates reports its result and returns to the document, and the startup check leaves focus and reading alone.
Add Russian, Ukrainian, French, Hebrew and German catalogs for every message of the interface and the command line, with mnemonics unique on every menu level and in every dialog, and ship the six languages. Tests build the real menu bar, the context menus and every dialog in each language and fail on a repeated mnemonic, check that every window is mirrored in Hebrew, and that each catalog loads and translates every message of the template. New-Language.ps1 no longer writes the Language header onto the Language-Team line.
All four checks pass: the switch to Russian applies live, Russian menus and mnemonics work, Hebrew mirrors the window and dialogs, and the document keeps its own language.
The manual covers reading, notes, tasks, settings, opening files, the command line and the keyboard and mouse reference. A base glossary for its translations sits beside it and is kept out of the build output.
Russian, Ukrainian, French, Hebrew and German manuals, with UI names and key names taken from each language's catalog.
Add the Inno Setup installer (from SIC's), with a new permanent AppId, the six interface languages, the .NET 10 Desktop and WebView2 runtimes as dependencies, and plancake on the machine PATH (taken off on uninstall). Build-Installer.ps1 compiles the translations, publishes, builds the installer and a portable zip carrying the same files plus an empty userdata folder, and optionally a signed appcast and an SCP deploy. CLAUDE.md documents the release steps and the wingetcreate command.
README for a general audience: what PlanCake is, installing (website, installer, portable zip, winget), reading and annotating, the command line, the pk alias, reviewing plans with Claude and Debussy, building. CLAUDE.md gains the architecture, the page protocol, the WebView2 and JAWS findings that shape the code, one window per file and the command-line output caveat; adding a language lists every place a language lives, and the update key is no longer called a placeholder. CHANGELOG.md and changelogs/1.0.0.md carry the 1.0.0 feature list. The command-line help opens with the tagline, in every catalog.
Security and safety: - Links never run a program or script: LinkResolver sorts them into LinkKind.Program (deny list, PATHEXT, AssocIsDangerous) and the window offers to show the file in File Explorer. UNC and file://host targets on another host than the document's are Unsupported and never touched. - Open in editor uses the "edit" verb or Notepad for non-Markdown files, never the default verb. - Downloads are always saved under a .md name and get a Zone.Identifier stream; a dropped connection or corrupt body maps to a network failure, and DownloadAndOpen catches everything. - Application.ThreadException is handled: logged, and the user decides whether to keep PlanCake open. - A file whose BOM does not decode, or UTF-8 with a few damaged bytes, is read-only with the line of the first bad byte, never decoded as a legacy code page; TryDecodeCleanly rejects NUL. A failed conversion to UTF-8 leaves the file open read-only instead of failing to open. - Editing, deleting or clearing a note without a closing marker is refused (GUI and plancake clear), since it would take the rest of the file. - The --output guard compares file identity (volume serial and file ID). - PlanCake.cfg is written through a temporary file and moved; Load retries; View > Interface language re-reads the file first (Config.SaveLanguage). - The file watcher backs off and stops after bounded retries, and decodes the file with the window's document language. - Ctrl+Shift+= zooms in too (Ctrl+Plus on US and UK layouts). Cleanups: removed the "not yet available" scaffolding, MenuBuilder (now MenuSpecExtensions), the placeholder update-key check (NotConfigured is Unavailable with an accurate message) and FocusLinesMessage; shared the marker-error texts, render strings, encoding name, culture check and EnsureWritable; one list-marker parser; one Nearest helper; App.WebFolder; String. for static members enforced through IDE0049. Tests: link kinds, downloads, damaged encodings, conversion failure, unterminated notes, file identity, config atomic save and SaveLanguage, watcher back-off and legacy decoding, CSP of index.html, every manual copied, mnemonics of every form, injected ANSI code page in CLI tests, tighter marker, restorer and config tests. Docs: README build order and behaviors, locale README, CLAUDE.md (tests, new dialogs, manuals, focus mark, data folder, link safety), JAWS findings of Tasks 12 and 15a, plan Task 15a record, six manuals, catalogs in five languages.
- Links: open only a folder or a passive document (plain text, PDF, a picture) with the system; any other local file is offered in File Explorer instead, since its default action may run it (.py with the Python launcher, .sh with Git for Windows, .rdp, .theme, .iso, ...). Those types also join the runnable list. - Notes: refuse to add a note after the start of a note without a closing marker (store, runner and before the dialog opens), since its closing marker would close the stray one and a later delete would take the rest of the file. - Delete all notes: take the rendered text before the question, so an outside change while it is open makes the delete stale instead of removing notes the user never saw. - Task check boxes: every render sets each check box from the file's state; a refused toggle (stale file, or a toggle from a replaced render) sets the check box back and says the file changed. - Settings: a window re-reads PlanCake.cfg when activated and it changed since it was last read or written, so settings saved in another window (note markers above all) apply there too. - New strings translated in de, fr, he, ru and uk; the manuals describe the link rule and the refused add.
- Config: split reading the settings file from applying it. The sections and the change stamp are replaced only after a read succeeds. The new Config.Reload, used by ReloadIfChanged, File > Settings and View > Interface language, keeps the settings in memory when the file exists but cannot be read or parsed, logs it, and leaves the stamp alone so the next activation tries again. Only the startup Load falls back to the defaults; a missing file is still written out with them. - SaveLanguage does not save when the re-read failed: the language applies for the session and the file is left as it is. - Tests for a locked file on activation, a malformed file on a re-read, at startup and in SaveLanguage, and a deleted file on a re-read.
- Claim the per-file pipe before opening a file: SingleInstance.TryClaim registers, or else activates the window holding the file, retrying when the holder lets go in between. Two windows opening the same file at once can no longer both write it. The initial file of a window that loses the race closes that window; a holder that does not answer is reported and the file is not opened. Tests cover concurrent claims. - Let the remaining windows take the background update checks over when the window doing them closes: a window without the claim retries it every minute and starts the checks at the interval Settings asks for. - Extract-Strings.ps1 writes messages.pot with LF endings and no blank line at the end, which git diff --check reported; the template is regenerated. - New message for a file held by a window that does not respond, translated in every catalog.
- A window taking the background update checks over no longer starts them at the interval it read earlier: it reads the settings file again first, as on activation (another window may have set Never since), and a file that cannot be read keeps the settings in memory. - The takeover waits while a dialog of the window is open, so the action that opened it finishes with the settings the user saw; the claim is let go for the next attempt. - UpdateService.TryTakeOver holds the decision apart from NetSparkle, with tests: nothing is read while another window holds the claim, the interval read at takeover wins, and a window that cannot read it now lets go.
- Write the takeover check as a plain null check and a deconstruction. The positional var pattern it replaces already failed on null (its parenthesized designation checks for null), so behaviour is unchanged: a window that cannot take the update checks over keeps retrying.
- A client that connected and closed without sending its request left the per-file pipe broken: Disconnect was skipped because IsConnected was false, and the listener then failed every wait at once, spinning a core, flooding the log and making the file impossible to open in any other window. The pipe is now disconnected after every connection, connected or broken. - A client that came and went before the listener waited made the wait fail with "The pipe is being closed" while the stream still counted itself as waiting to connect; the pipe is now reset through DisconnectNamedPipe. - Consecutive listener failures back off from 50 ms to 1 s, and only the first of a run is logged as a warning, so no unexpected state can become a busy loop. - Tests: activation still works after a client left without asking, both before and while the window waits for it.
xUnit runs synchronous tests on thread pool threads, so on a small CI runner a few blocking tests hold every thread the pool starts with. Timer callbacks and the pipe listener's continuations then wait for the pool to grow, which the fixed windows of two tests did not allow. MarkdownFile gets an OnRetry option, and the unlock test frees the lock when the first attempt has failed instead of on a timer. The single-instance listener no longer depends on a free pool thread to recover or to finish a request: it listens again at once after a single failed connection and backs off only on failures in a row, it waits for the client to close instead of blocking in WaitForPipeDrain, and TryActivate connects on the calling thread instead of blocking a pool thread in ConnectAsync. Tests place the broken client through listener hooks instead of a sleep, run the blocking calls on threads of their own as the app does on the window's thread, and run apart from the other tests.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
PlanCake 1.0: a Windows desktop application that shows a Markdown file (above all a long implementation plan) as rendered HTML in WebView2 and lets you leave notes on any paragraph, list item, heading, table row or code block. Notes go straight into the
.mdfile between[usernote]…[/usernote]markers, the convention Claude's manual-review step already understands. There is no Save command and no side file: the file on disk is the single source of truth.Reading raw Markdown in an editor means wading through punctuation with no heading, list or table navigation; rendering fixes the reading. Annotating a specific place from a web page's reading cursor is the hard part, and PlanCake solves it by annotating blocks: every rendered block knows the source lines it came from, so Enter or a click on it puts the note in exactly the right place in the file.
The same executable also works headless (
plancake list|check|clear|export), which is what Claude uses during manual review.Tasks completed
docs/jaws-spike.md, which reshaped Tasks 6 and 7)Every UI task was checked with JAWS by the maintainer; the results and the changes they caused are recorded in the plan (
docs/plans/completed/001-plan-cake-v1.md) anddocs/jaws-spike.md.Review
messages.pot. One claim (avarpattern that "always matches") was refuted with a compiled check and the line was rewritten plainly anyway.Validation
All passing at the head of the branch:
pwsh -NoProfile -File ./src/PlanCake/locale/scripts/Compile-Translations.ps1 -Strict(5 languages, no fuzzy entries)dotnet build(0 warnings)dotnet test(858 tests)dotnet format --verify-no-changesinstaller/Build-Installer.ps1builds the installer and the portable zip; a real install of v1.0.0.57 was run and checked (wizard read fully with JAWS, Program Files, machine PATH, Start menu,plancake check)To check by hand
"noteMarkers": ["[usernote]...[/usernote]"]in~/.claude/debussy.json. If!USERNOTE!stays in that list for hand-written notes,plancake checkwill not count those, since PlanCake reads only its own markers. A separate change in Debussy: plan-make's manual-review hand-off can startplancake <plan>when it is on the PATH, and manual review can useplancake list --jsonandplancake check.Set-Alias pk plancaketo the PowerShell profile (andalias pk=plancakein bash).plancake.oire.devfor the appcast and downloads (deploy.json), create the GitHub release, and submit the winget manifest withwingetcreateat the first release.