diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 310469c..3f5d6d6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,6 +9,10 @@ on: description: Also build the Intel macOS app and dmg (x86_64-apple-darwin) type: boolean default: false + release: + description: Publish a GitHub release v from main with every bundle (tick intel too) + type: boolean + default: false jobs: test: @@ -118,3 +122,39 @@ jobs: src-tauri/target/x86_64-apple-darwin/release/bundle/** if-no-files-found: error retention-days: 3 + + # Run on demand from main (Actions, "Run workflow", tick release and intel): tags + # v on this commit and publishes the bundles, a SHA256SUMS.txt + # and that version's CHANGELOG.md section as a GitHub release. + release: + name: release + if: >- + !cancelled() && github.event_name == 'workflow_dispatch' && inputs.release && + github.ref == 'refs/heads/main' && needs.desktop.result == 'success' && + (needs.desktop-intel.result == 'success' || needs.desktop-intel.result == 'skipped') + needs: [desktop, desktop-intel] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + path: bundles + - name: Collect the installers + shell: bash + run: | + mkdir out + find bundles -type f \( -name '*.dmg' -o -name '*-setup.exe' -o -name '*.msi' -o -name '*.deb' -o -name '*.AppImage' \) -exec cp {} out/ \; + (cd out && sha256sum * > SHA256SUMS.txt) + ls -la out + cat out/SHA256SUMS.txt + - name: Publish + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + version=$(node -p "require('./package.json').version") + awk -v v="## $version" '$0 ~ "^"v {on=1; next} on && /^## / {exit} on' CHANGELOG.md > notes.md + test -s notes.md || { echo "CHANGELOG.md has no section for $version"; exit 1; } + gh release create "v$version" out/* --target "$GITHUB_SHA" --title "Folio $version" --notes-file notes.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..13df543 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +## 0.3.0 (2026-10-20) + +The first public release. Folio is now open source under the MIT license. + +- Bundles opencode 2.0.32 (was 2.0.28), with the six release archives pinned by SHA-256. The 2.0.28 to 2.0.32 API change is additive; a live run against 2.0.32 passed. +- Chapters are named by opencode from what you asked. Folio used to create sessions titled "Untitled", which stopped opencode from naming them. +- A drop cap opens a chapter only when the paragraph starts with a letter or digit and runs at least as many lines as the cap is tall. +- Footnote marks read as marks: runs collapse to "3–7", and the numbers no longer stretch the line they sit on. +- Your words in ink are set ragged right, even when the theme justifies text. +- Questions from opencode show their Markdown (italics, code) instead of the raw asterisks. +- A code-mode script reads "Ran a script." in its footnote. +- Reject in flight: rejecting a plate while opencode is still finishing the turn no longer loses the plate when its id changes. +- The drop cap is whole. It hung below its one-line box and was cut off, so an "I" could read as a "T". +- Reject on a plate whose turn opencode hasn't named yet says to try again in a moment, instead of reading as final. + +### Install + +Download from the assets below. The builds are not code-signed yet. + +- **macOS 12 or later** (`Folio_0.3.0_aarch64.dmg` for Apple silicon, `Folio_0.3.0_x64.dmg` for Intel): drag Folio to Applications and open it. When macOS says it can't check the app, open System Settings → Privacy & Security and click **Open Anyway**. If macOS says Folio is damaged, run `xattr -dr com.apple.quarantine /Applications/Folio.app` in Terminal and open it again. +- **Windows 10 or later** (`Folio_0.3.0_x64-setup.exe` or `Folio_0.3.0_x64_en-US.msi`): run the installer. If SmartScreen stops it, click **More info**, then **Run anyway**. +- **Linux x86_64** (`Folio_0.3.0_amd64.deb` or `Folio_0.3.0_amd64.AppImage`): `sudo apt install ./Folio_0.3.0_amd64.deb`, or make the AppImage executable and run it. + +`SHA256SUMS.txt` lists a digest for every file. + +## 0.2.0 (2026-10-14) + +The first complete build: the book, chapters, footnotes, plates with Accept, Revise, Reject and Fold back, margin notes, ask before the press writes, questions as forms, six themes, the bundled press (opencode 2.0.28), and desktop bundles for macOS, Windows and Linux. diff --git a/README.md b/README.md index 1486af3..ed82752 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Folio is an open-source desktop reading room for [opencode](https://opencode.ai), by [Basalt](https://hellobasalt.dev). A repository is a book. Each opencode session is a chapter. You write on the page. +Website and downloads: [folio.hellobasalt.dev](https://folio.hellobasalt.dev/). Every release is on the [releases page](https://github.com/dpdev69/folio/releases), with install steps in [CHANGELOG.md](CHANGELOG.md). + The shell is Tauri 2. The pages are React, TypeScript, and Vite. opencode is not forked: Folio runs `opencode serve` as a separate process, which this document calls the press, and speaks its HTTP API. opencode is MIT licensed; the notice is in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). ## What the first launch does @@ -184,6 +186,12 @@ The CI macOS job passes the certificate, identity, and Apple ID variables throug codesign -dv --verbose=2 src-tauri/target/aarch64-apple-darwin/release/bundle/macos/Folio.app ``` +## Website + +`site/` is the static site at [folio.hellobasalt.dev](https://folio.hellobasalt.dev/): plain HTML and CSS with no build step, served by Cloudflare Pages from `main` (output directory `site`). `site/_headers` sets the security headers. The screenshots in `site/assets/` are taken from the real app. + ## CI `.github/workflows/build.yml` runs the unit tests and the web build, then fetches the bundled press and runs `tauri build` on Linux (deb, AppImage), macOS (app, dmg), and Windows (nsis, msi). + +To publish a release, bump the version in `package.json`, `src-tauri/tauri.conf.json` and `src-tauri/Cargo.toml`, add its section to `CHANGELOG.md`, merge to `main`, then run the workflow from `main` with **release** (and **intel**) ticked. The `release` job tags `v` on that commit and attaches the dmg, the Windows installers, the deb, the AppImage and `SHA256SUMS.txt`, with the changelog section as the notes. diff --git a/docs/e2e.md b/docs/e2e.md index 40accae..a4decda 100644 --- a/docs/e2e.md +++ b/docs/e2e.md @@ -88,6 +88,8 @@ The scratch repository was `greeter` (`src/greet.ts`, `src/shout.ts`, `src/index Found in this run and left for the interface pass: the drop cap in row 41; the toggle in the menu leaves the menu open (row 39); and "This plate cannot be set back on its own." for a plate whose turn is not named yet (row 38) reads as final, though Reject works a moment later. Not tested here: the packaged window itself (rows 17, 18 and 21 to 22 cover it on 2.0.28), the margin note, the question form and a window resize. +After this run: the drop cap of row 41 is whole (`.line.with-drop` shows its overflow and clips only its words; checked in the browser, cap 145–187 px over its 143–172 px line), and the refusal of row 38 now reads "The press is still setting this plate. Reject it again in a moment." while the plate's turn is still `local-…`. The menu staying open after the ask toggle is kept: it shows the new state. + ## Notes - Defect fixes routed into this run: footnote paths from the book's root (`3ad63c6`), room at the page foot diff --git a/package-lock.json b/package-lock.json index e273359..2859add 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "folio", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "folio", - "version": "0.2.0", + "version": "0.3.0", "dependencies": { "@fontsource/caveat": "^5.2.8", "@fontsource/eb-garamond": "^5.2.7", diff --git a/package.json b/package.json index 68f4184..74a2777 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "folio", "private": true, "license": "MIT", - "version": "0.2.0", + "version": "0.3.0", "description": "Folio is a book-skinned desktop client for the opencode server.", "type": "module", "opencode": { diff --git a/site/404.html b/site/404.html new file mode 100644 index 0000000..b9493b0 --- /dev/null +++ b/site/404.html @@ -0,0 +1,99 @@ + + + + + +Page not found · Folio + + + + + + + + + + + + + + + +
+
+

404

+

This page isn't in the book.

+

The link may be old, or the page was never set.

+

Back to Folio

+
+
+ + + + diff --git a/site/_headers b/site/_headers new file mode 100644 index 0000000..d9693e7 --- /dev/null +++ b/site/_headers @@ -0,0 +1,16 @@ +/* + Content-Security-Policy: default-src 'self'; img-src 'self' data:; style-src 'self'; font-src 'self'; script-src 'self' https://static.cloudflareinsights.com; connect-src 'self' https://cloudflareinsights.com; base-uri 'self'; form-action 'none'; frame-ancestors 'none' + Strict-Transport-Security: max-age=31536000; includeSubDomains + X-Content-Type-Options: nosniff + X-Frame-Options: DENY + Referrer-Policy: strict-origin-when-cross-origin + Permissions-Policy: camera=(), microphone=(), geolocation=() + +/assets/* + Cache-Control: public, max-age=86400 + +/assets/fonts/* + Cache-Control: public, max-age=31536000, immutable + +https://:project.pages.dev/* + X-Robots-Tag: noindex diff --git a/site/assets/card-actions.webp b/site/assets/card-actions.webp new file mode 100644 index 0000000..dc03de2 Binary files /dev/null and b/site/assets/card-actions.webp differ diff --git a/site/assets/card-contents.webp b/site/assets/card-contents.webp new file mode 100644 index 0000000..827728b Binary files /dev/null and b/site/assets/card-contents.webp differ diff --git a/site/assets/card-ink.webp b/site/assets/card-ink.webp new file mode 100644 index 0000000..ca59804 Binary files /dev/null and b/site/assets/card-ink.webp differ diff --git a/site/assets/card-notes.webp b/site/assets/card-notes.webp new file mode 100644 index 0000000..d5f34da Binary files /dev/null and b/site/assets/card-notes.webp differ diff --git a/site/assets/favicon.png b/site/assets/favicon.png new file mode 100644 index 0000000..a85d2df Binary files /dev/null and b/site/assets/favicon.png differ diff --git a/site/assets/fonts/OFL-Caveat.txt b/site/assets/fonts/OFL-Caveat.txt new file mode 100644 index 0000000..5303054 --- /dev/null +++ b/site/assets/fonts/OFL-Caveat.txt @@ -0,0 +1,93 @@ +Copyright 2014 The Caveat Project Authors (https://github.com/googlefonts/caveat) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/site/assets/fonts/OFL-EB-Garamond.txt b/site/assets/fonts/OFL-EB-Garamond.txt new file mode 100644 index 0000000..da3ca89 --- /dev/null +++ b/site/assets/fonts/OFL-EB-Garamond.txt @@ -0,0 +1,93 @@ +Copyright 2017 The EB Garamond Project Authors (https://github.com/octaviopardo/EBGaramond12) EBGaramond-Italic[wght].ttf: Copyright 2017 The EB Garamond Project Authors (https://github.com/octaviopardo/EBGaramond12) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/site/assets/fonts/caveat-latin-500-normal.woff2 b/site/assets/fonts/caveat-latin-500-normal.woff2 new file mode 100644 index 0000000..a1feeaf Binary files /dev/null and b/site/assets/fonts/caveat-latin-500-normal.woff2 differ diff --git a/site/assets/fonts/eb-garamond-latin-400-italic.woff2 b/site/assets/fonts/eb-garamond-latin-400-italic.woff2 new file mode 100644 index 0000000..f833540 Binary files /dev/null and b/site/assets/fonts/eb-garamond-latin-400-italic.woff2 differ diff --git a/site/assets/fonts/eb-garamond-latin-400-normal.woff2 b/site/assets/fonts/eb-garamond-latin-400-normal.woff2 new file mode 100644 index 0000000..b51664d Binary files /dev/null and b/site/assets/fonts/eb-garamond-latin-400-normal.woff2 differ diff --git a/site/assets/fonts/eb-garamond-latin-500-italic.woff2 b/site/assets/fonts/eb-garamond-latin-500-italic.woff2 new file mode 100644 index 0000000..f7e45ef Binary files /dev/null and b/site/assets/fonts/eb-garamond-latin-500-italic.woff2 differ diff --git a/site/assets/fonts/eb-garamond-latin-500-normal.woff2 b/site/assets/fonts/eb-garamond-latin-500-normal.woff2 new file mode 100644 index 0000000..9b5ae45 Binary files /dev/null and b/site/assets/fonts/eb-garamond-latin-500-normal.woff2 differ diff --git a/site/assets/icon-256.png b/site/assets/icon-256.png new file mode 100644 index 0000000..62f3acc Binary files /dev/null and b/site/assets/icon-256.png differ diff --git a/site/assets/og-image.png b/site/assets/og-image.png new file mode 100644 index 0000000..9bb8568 Binary files /dev/null and b/site/assets/og-image.png differ diff --git a/site/assets/shot-ask.webp b/site/assets/shot-ask.webp new file mode 100644 index 0000000..4fa10e9 Binary files /dev/null and b/site/assets/shot-ask.webp differ diff --git a/site/assets/shot-plate-crop.webp b/site/assets/shot-plate-crop.webp new file mode 100644 index 0000000..627afae Binary files /dev/null and b/site/assets/shot-plate-crop.webp differ diff --git a/site/assets/shot-plate.webp b/site/assets/shot-plate.webp new file mode 100644 index 0000000..b4c500c Binary files /dev/null and b/site/assets/shot-plate.webp differ diff --git a/site/assets/shot-themes.webp b/site/assets/shot-themes.webp new file mode 100644 index 0000000..ebf1612 Binary files /dev/null and b/site/assets/shot-themes.webp differ diff --git a/site/assets/shot-title.webp b/site/assets/shot-title.webp new file mode 100644 index 0000000..e74abb7 Binary files /dev/null and b/site/assets/shot-title.webp differ diff --git a/site/assets/site.css b/site/assets/site.css new file mode 100644 index 0000000..8b01f36 --- /dev/null +++ b/site/assets/site.css @@ -0,0 +1,214 @@ +@font-face { font-family: "EB Garamond"; font-style: normal; font-weight: 400; font-display: swap; src: url("fonts/eb-garamond-latin-400-normal.woff2") format("woff2"); } +@font-face { font-family: "EB Garamond"; font-style: italic; font-weight: 400; font-display: swap; src: url("fonts/eb-garamond-latin-400-italic.woff2") format("woff2"); } +@font-face { font-family: "EB Garamond"; font-style: normal; font-weight: 500; font-display: swap; src: url("fonts/eb-garamond-latin-500-normal.woff2") format("woff2"); } +@font-face { font-family: "EB Garamond"; font-style: italic; font-weight: 500; font-display: swap; src: url("fonts/eb-garamond-latin-500-italic.woff2") format("woff2"); } +@font-face { font-family: Caveat; font-style: normal; font-weight: 500; font-display: swap; src: url("fonts/caveat-latin-500-normal.woff2") format("woff2"); } + +/* Folio's own palette: the desk, the page, the ink. */ +:root { + --desk: #ebe4d8; + --desk-2: #e3dbcf; + --page: #f6f1e8; + --ink: #2c261f; + --soft: #5c5348; + --rule: #ddd2c1; + --terra: #a34c2e; + --terra-deep: #8c3f25; + --quill: #1c3d72; + --card: #fbf8f2; + --shadow: 62, 44, 28; + --serif: "EB Garamond", "Iowan Old Style", Palatino, "Palatino Linotype", Georgia, serif; + --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; + --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + color-scheme: light dark; +} +@media (prefers-color-scheme: dark) { + :root { + --desk: #141210; + --desk-2: #0e0d0b; + --page: #1c1a17; + --ink: #e8e2d6; + --soft: #b3a998; + --rule: #34302a; + --terra: #e0896a; + --terra-deep: #eba085; + --quill: #9db4e0; + --card: #211e1a; + --shadow: 0, 0, 0; + } +} + +* { box-sizing: border-box; } +html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; } +@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } } +body { + margin: 0; + background: var(--desk); + color: var(--ink); + font: 400 19px/1.55 var(--serif); + overflow-x: hidden; +} +img { max-width: 100%; height: auto; display: block; } +a { color: var(--terra); text-underline-offset: 3px; } +a:hover { color: var(--terra-deep); } +:focus-visible { outline: 2px solid var(--terra); outline-offset: 3px; border-radius: 4px; } +code { font-family: var(--mono); font-size: 0.82em; background: color-mix(in srgb, var(--ink) 7%, transparent); padding: 0.1em 0.35em; border-radius: 4px; } +.wrap { width: min(1120px, 100% - 32px); margin-inline: auto; } +.skip { position: absolute; left: -9999px; top: 8px; background: var(--ink); color: var(--page); padding: 8px 12px; border-radius: 8px; z-index: 10; } +.skip:focus { left: 8px; } + +/* Header */ +.nav { position: sticky; top: 0; z-index: 5; background: color-mix(in srgb, var(--desk) 86%, transparent); backdrop-filter: saturate(1.4) blur(12px); -webkit-backdrop-filter: saturate(1.4) blur(12px); border-bottom: 1px solid transparent; } +.nav-inner { display: flex; align-items: center; justify-content: space-between; gap: 16px; height: 64px; } +.brand-lockup { display: flex; align-items: center; min-width: 0; } +.brand { display: inline-flex; align-items: center; gap: 10px; color: var(--ink); text-decoration: none; font-size: 24px; font-weight: 500; letter-spacing: 0.01em; } +.brand:hover { color: var(--ink); } +.brand img { width: 28px; height: 28px; border-radius: 6px; } +.by-basalt { font: 400 0.75rem/1 var(--mono); letter-spacing: 0.04em; color: var(--soft); text-decoration: none; margin-left: 10px; padding-top: 4px; white-space: nowrap; } +.by-basalt:hover { color: var(--ink); text-decoration: underline; } +.nav-links { display: flex; gap: 28px; font-size: 17px; } +.nav-links a { color: var(--soft); text-decoration: none; } +.nav-links a:hover { color: var(--ink); } +.nav-end { display: flex; align-items: center; gap: 14px; } +.nav-gh { display: inline-flex; align-items: center; gap: 6px; color: var(--soft); text-decoration: none; font-size: 17px; } +.nav-gh svg { width: 18px; height: 18px; } +.nav-gh:hover { color: var(--ink); } + +/* Buttons */ +.btn { display: inline-flex; align-items: center; justify-content: center; gap: 9px; border-radius: 999px; text-decoration: none; font: 500 15px/1 var(--sans); padding: 12px 20px; border: 1px solid transparent; transition: background-color .15s, color .15s, border-color .15s; } +.btn svg { width: 18px; height: 18px; } +.btn-primary { background: var(--ink); color: var(--page); } +.btn-primary:hover { background: color-mix(in srgb, var(--ink) 86%, var(--terra)); color: var(--page); } +.btn-secondary { background: transparent; color: var(--ink); border-color: var(--rule); } +.btn-secondary:hover { color: var(--ink); border-color: var(--soft); } +.btn-sm { padding: 9px 15px; font-size: 14px; } +.btn-lg { padding: 15px 26px; font-size: 16px; } + +/* Hero: the headline in the middle, real pieces of the app around it. */ +.hero { position: relative; padding: 72px 0 40px; } +.hero-center { position: relative; z-index: 2; max-width: 620px; margin: 0 auto; text-align: center; } +.kicker { font: 500 13px/1 var(--sans); letter-spacing: 0.16em; text-transform: uppercase; color: var(--soft); margin: 0 0 22px; } +.hero h1 { font-weight: 500; font-size: clamp(44px, 6.4vw, 78px); line-height: 0.98; letter-spacing: -0.015em; margin: 0; } +.hero h1 em { color: var(--terra); } +.lede { font-size: clamp(19px, 2.2vw, 22px); line-height: 1.5; color: var(--soft); margin: 26px auto 0; max-width: 28em; } +.actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 32px; } +.trust { font: 400 14px/1.5 var(--sans); color: var(--soft); margin: 16px 0 0; } +.trust a { color: var(--soft); } + +.floats { position: absolute; inset: 0; z-index: 1; pointer-events: none; } +.float { position: absolute; margin: 0; background: var(--card); border-radius: 12px; box-shadow: 0 1px 2px rgba(var(--shadow), .10), 0 14px 34px -10px rgba(var(--shadow), .30); overflow: hidden; } +.float img { width: 100%; } +.f1 { width: 220px; left: 0; top: 10px; transform: rotate(-4deg); } +.f2 { width: 210px; left: 20px; top: 300px; transform: rotate(3deg); } +.f3 { width: 220px; right: 0; top: 0; transform: rotate(4deg); } +.f4 { width: 220px; right: 14px; top: 290px; transform: rotate(-3deg); } +@media (max-width: 1160px) { .floats { display: none; } .hero { padding-top: 56px; } } + +.hero-shot { position: relative; z-index: 2; margin: 64px auto 0; max-width: 1100px; } +.frame { margin: 0; border-radius: 14px; overflow: hidden; background: var(--desk-2); box-shadow: 0 1px 2px rgba(var(--shadow), .10), 0 30px 70px -24px rgba(var(--shadow), .45); border: 1px solid color-mix(in srgb, var(--ink) 8%, transparent); } +.frame img { width: 100%; } +.caption { font-style: italic; color: var(--soft); text-align: center; font-size: 17px; margin: 16px auto 0; max-width: 46em; } + +/* Sections */ +.section { padding: 96px 0; } +.section + .section { border-top: 1px solid var(--rule); } +.eyebrow { font: 500 13px/1 var(--sans); letter-spacing: 0.16em; text-transform: uppercase; color: var(--terra); margin: 0 0 14px; } +.section-head { max-width: 720px; margin: 0 auto 56px; text-align: center; } +.section-head h2 { font-weight: 500; font-size: clamp(34px, 4.6vw, 52px); line-height: 1.05; letter-spacing: -0.01em; margin: 0; } +.section-head p { color: var(--soft); font-size: 20px; margin: 18px 0 0; } + +.glossary { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 20px; margin: 0; padding: 0; list-style: none; } +.glossary li { background: var(--page); border: 1px solid var(--rule); border-radius: 12px; padding: 24px 22px 22px; } +.glossary b { display: block; font-weight: 500; font-size: 26px; line-height: 1.1; } +.glossary .is { display: block; font: 500 12px/1 var(--sans); letter-spacing: 0.14em; text-transform: uppercase; color: var(--soft); margin: 8px 0 12px; } +.glossary p { margin: 0; color: var(--soft); font-size: 17.5px; line-height: 1.5; } +.ink { font-family: Caveat, cursive; color: var(--quill); font-size: 1.22em; line-height: 1.1; } +.mark { color: var(--terra); font-size: 0.7em; position: relative; top: -0.5em; } + +.feature { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: 56px; align-items: center; } +.feature + .feature { margin-top: 96px; } +.feature.flip { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); } +.feature.flip .feature-copy { order: 2; } +.feature h3 { font-weight: 500; font-size: clamp(28px, 3.4vw, 38px); line-height: 1.1; margin: 0 0 16px; } +.feature p { margin: 0 0 14px; color: var(--soft); } +.feature ul { margin: 0; padding-left: 1.1em; color: var(--soft); } +.feature li { margin: 0 0 8px; } +.feature li b { color: var(--ink); font-weight: 500; } + +/* The catch */ +.catch { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; margin: 0; padding: 0; list-style: none; } +.catch li { background: var(--page); border: 1px solid var(--rule); border-radius: 12px; padding: 22px 24px; } +.catch h3 { font-weight: 500; font-size: 23px; margin: 0 0 8px; } +.catch p { margin: 0; color: var(--soft); font-size: 17.5px; } + +/* Download */ +.download { text-align: center; } +.platforms { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; text-align: left; margin-top: 8px; } +.platform { background: var(--page); border: 1px solid var(--rule); border-radius: 12px; padding: 24px; display: flex; flex-direction: column; } +.platform h3 { font-weight: 500; font-size: 26px; margin: 0 0 4px; } +.platform .files { font: 400 13px/1.5 var(--mono); color: var(--soft); margin: 0 0 14px; } +.platform ol { margin: 0 0 18px; padding-left: 1.2em; color: var(--soft); font-size: 17px; } +.platform li { margin-bottom: 6px; } +.platform .btn { margin-top: auto; align-self: flex-start; } +.note { color: var(--soft); font-size: 17px; max-width: 46em; margin: 28px auto 0; } + +/* FAQ */ +.faq { max-width: 780px; margin: 0 auto; } +.faq details { border-bottom: 1px solid var(--rule); } +.faq summary { cursor: pointer; list-style: none; padding: 20px 36px 20px 0; position: relative; font-size: 22px; font-weight: 500; } +.faq summary::-webkit-details-marker { display: none; } +.faq summary::after { content: "+"; position: absolute; right: 4px; top: 16px; font-size: 28px; color: var(--soft); font-weight: 400; } +.faq details[open] summary::after { content: "−"; } +.faq details p { margin: 0 0 20px; color: var(--soft); } + +/* Documents: privacy, terms, 404 */ +.doc { padding: 64px 0 96px; } +.doc-wrap { max-width: 760px; } +.doc h1 { font-weight: 500; font-size: clamp(40px, 6vw, 58px); line-height: 1.05; margin: 0 0 8px; } +.doc-meta { color: var(--soft); font-style: italic; margin: 0 0 36px; } +.doc h2 { font-weight: 500; font-size: 28px; margin: 40px 0 10px; } +.doc p, .doc li { color: var(--soft); } +.doc li { margin-bottom: 8px; } +.doc b { color: var(--ink); font-weight: 500; } +.doc .lede-doc { font-size: 22px; color: var(--ink); } + +/* Basalt product footer (basalt/footer), mapped to Folio's tokens. */ +.footer { --bg-2: var(--desk-2); --ink-2: var(--ink); --ink-3: var(--soft); font-family: var(--sans); } +.footer { border-top: 1px solid var(--rule); background: var(--bg-2); padding-block: 48px 36px; font-size: 14px; } +.footer .wrap { width: min(1080px, 100% - 32px); margin-inline: auto; } +.footer .brand { display: inline-flex; align-items: center; gap: 10px; color: var(--ink); font-weight: 600; font-size: 18px; text-decoration: none; font-family: var(--serif); } +.footer .brand img { width: 24px; height: 24px; display: block; } +.footer-grid { display: grid; grid-template-columns: minmax(0, 1.6fr) repeat(3, minmax(0, 1fr)); gap: 32px; } +.footer-brand p { color: var(--ink-3); margin: 10px 0 0; max-width: 26em; } +.footer nav { display: grid; gap: 8px; align-content: start; } +.footer nav a { color: var(--ink-2); text-decoration: none; } +.footer nav a:hover { text-decoration: underline; text-underline-offset: 3px; } +.f-title { font-weight: 600; color: var(--ink); margin: 0 0 4px; } +.footer-base { margin-top: 40px; padding-top: 20px; border-top: 1px solid var(--rule); display: grid; gap: 8px; color: var(--ink-3); font-size: 12.5px; } +.footer-base p { margin: 0; } +.footer-base a { color: var(--ink-2); } + +@media (max-width: 900px) { + .nav-links { display: none; } + .glossary { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .feature, .feature.flip { grid-template-columns: minmax(0, 1fr); gap: 28px; } + .feature.flip .feature-copy { order: 0; } + .catch, .platforms { grid-template-columns: minmax(0, 1fr); } + .section { padding: 72px 0; } +} +@media (max-width: 800px) { .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } .footer-brand { grid-column: 1 / -1; } } +@media (max-width: 560px) { + body { font-size: 18px; } + .glossary { grid-template-columns: minmax(0, 1fr); } + .nav-gh span { display: none; } + .hero-shot { margin-top: 44px; } +} +@media (max-width: 360px) { .by-basalt { display: none; } } +.sprite { position: absolute; width: 0; height: 0; overflow: hidden; } +.hero-wrap { position: relative; } +.doc-wrap.center { text-align: center; } +.doc .lede-doc.soft { color: var(--soft); } +.doc .back { margin-top: 28px; } +.section-head .eyebrow { color: var(--terra); font-size: 13px; margin: 0 0 14px; } +.trust a { white-space: nowrap; } +.doc .eyebrow { color: var(--terra); } diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..ef0721a --- /dev/null +++ b/site/index.html @@ -0,0 +1,282 @@ + + + + + +Folio: read your opencode sessions like a book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+

A reading room for opencode

+

Your coding agent, set as a book.

+

Folio is a desktop app for opencode. Each session becomes a chapter you can read: your request in ink, every command a footnote, every edit a plate you accept or reject.

+ +

Free · MIT · macOS, Windows and Linux · version 0.3.0

+
+ +
+
A Folio spread titled Chapter I, Add farewell function to src/greet.ts. The left page opens with a drop cap, then the request in blue handwriting, the reply in prose and eight footnotes for the files the agent read and the commands it ran. The right page shows Plate I, the diff for src/greet.ts, with Accept, Revise, Reject and Fold back under it.
+

A real session on opencode 2.0.32. opencode named the chapter from the request, and the edit it made faces the page as Plate I.

+
+
+
+ +
+
+
+

How it reads

+

A terminal scrolls past. A book keeps its place.

+

Folio doesn't change what opencode does. It changes how you read it, so you can follow a long session the way you follow a chapter.

+
+
    +
  • The bookis a repository

    Open a folder and it becomes the book. Contents lists every chapter, the ribbon marks your place and search looks through all of it.

  • +
  • A chapteris a session

    opencode names each one from what you asked. You write in ink at the foot of the page and press Return twice to send.

  • +
  • Footnotesare commands

    Every file read and command run settles into a numbered note3–7 under the prose, so the reasoning stays readable.

  • +
  • Platesare changes

    Each edit is a diff set on the facing page, captioned like a figure: Plate I, the file, lines added and removed.

  • +
+
+
+ +
+
+
+

You stay in charge

+

Read the change before you keep it.

+
+ +
+
+

Every edit is a plate.

+

When opencode changes a file, the diff faces the page that explains it. Four words sit under it:

+
    +
  • Accept marks the plate as set.
  • +
  • Revise puts “Revise the change in …” in the ink so you can say what you want instead.
  • +
  • Reject reverses that plate's own patch on disk and tells opencode you declined it on purpose, so it doesn't try again. Later edits to the same file are left alone.
  • +
  • Fold back puts the plate away until you want it.
  • +
+
+
Plate I, src/greet.ts, plus four lines: a new farewell function under greet, with Accept, Revise, Reject and Fold back underneath.
+
+ +
+
+

Ask before it writes.

+

Turn on Ask before the press writes in the menu and opencode stops before each edit and each shell command. The question sits at the foot of the page with the proposed change facing it.

+

The answers are Allow once and No. There's deliberately no “Always”: in opencode that saves a rule for the whole project, and one click would quietly switch the setting off.

+
+
Chapter II paused before an edit: the request in ink on the left, May I edit src/greet.ts? Allow once · No at the foot of the page, and the proposed one-line comment set as Plate I on the right.
+
+ +
+
+

It brings its own opencode.

+

The installer carries opencode 2.0.32 and starts it for you on a random local port, with a password made for that launch. It keeps its own sessions and keys, so your opencode setup stays untouched.

+

The free OpenCode Zen models work without a key. Paste a key for another provider if you have one, or attach Folio to an opencode server you already run.

+
+
Folio's title page: The press is ready. Using opencode 2.0.32 with a free OpenCode Zen model. Open a folder, use a provider key, or read the sample.
+
+ +
+
+

Six themes, light and dark.

+

Original, Quiet, Paper, Bold, Calm and Focus each pair a typeface with a palette in both appearances. Text size, line spacing, margins and justified text are yours to set, and every theme keeps its ink readable on the page.

+

Highlight a passage to write in the margin. The reply sits beside your note and can replace the passage.

+
+
A chapter in the dark appearance with the Calm theme, and the Themes and Settings menu open: text size, appearance, and six swatches, Original, Quiet, Paper, Bold, Calm and Focus.
+
+
+
+ +
+
+
+

The catch

+

What you should know first.

+

Folio is young. Here is what that means today.

+
+
    +
  • It's early.

    This is version 0.3. The core works and has 364 tests behind it, but expect rough edges, and please report them.

  • +
  • The builds aren't signed yet.

    macOS and Windows will warn the first time you open it. The steps to get past that are below, next to each download.

  • +
  • Free models come and go.

    OpenCode Zen decides which free models exist and how much you can use them. They can be busy or rate limited. A provider key avoids that.

  • +
  • It's an agent on your machine.

    opencode reads files and runs commands in the folder you open, as it does in the terminal. Turn on Ask before the press writes if you want to approve each step.

  • +
  • It's a reader, not an editor.

    There's no file tree, terminal or code editor. Keep your editor open next to it; Folio is for following and steering the agent.

  • +
  • Intel Macs are untested.

    The Intel dmg is cross-built on an Apple silicon machine and hasn't been run on an Intel Mac yet. If it misbehaves, tell us.

  • +
+
+
+ +
+
+
+

Download

+

Get Folio 0.3.0.

+

Every file is on the GitHub release page, built by GitHub Actions from the public source.

+
+
+
+

macOS

+

.dmg · macOS 12+ · Apple silicon or Intel

+
    +
  1. Open the dmg and drag Folio to Applications.
  2. +
  3. Open Folio. When macOS says it can't check it, open System Settings → Privacy & Security and click Open Anyway.
  4. +
  5. If macOS says Folio is damaged, run xattr -dr com.apple.quarantine /Applications/Folio.app in Terminal, then open it again.
  6. +
+ Download for Mac +
+
+

Windows

+

-setup.exe or .msi · Windows 10+ · x64

+
    +
  1. Run the installer.
  2. +
  3. If SmartScreen says it protected your PC, click More info, then Run anyway.
  4. +
+ Download for Windows +
+
+

Linux

+

.deb or .AppImage · x86_64

+
    +
  1. Debian and Ubuntu: sudo apt install ./ followed by the .deb file name.
  2. +
  3. Anything else: make the AppImage executable and run it.
  4. +
+ Download for Linux +
+
+

Rather build it yourself? You need Node 22 and Rust. The README has the three commands.

+
+
+ +
+
+
+

FAQ

+

Questions

+
+
+
Is Folio free?

Yes. Folio is open source under the MIT license. There is no account and nothing to buy. The free OpenCode Zen models need no key; other providers bill you directly if you paste their key.

+
Do I need to install opencode first?

No. The installer carries opencode 2.0.32 and starts it for you on a random local port with a password made for that launch. You can also attach Folio to an opencode server you started yourself.

+
Does Folio replace opencode?

No. opencode does the work. Folio is a different way to read and steer it: it talks to opencode's own HTTP API and does not fork or patch it.

+
Will it touch my own opencode setup?

The bundled opencode keeps its sessions, keys and cache in Folio's own app data folder. It never opens your opencode database, and your opencode config folder stays yours.

+
Does Folio send my code anywhere?

Folio itself sends nothing to Basalt. opencode sends your prompts and the files it reads to the model provider you choose, the same as it does in the terminal.

+
macOS says it can't check Folio for malware. What do I do?

The build isn't signed with an Apple Developer ID yet. Open System Settings, then Privacy & Security, and click Open Anyway next to the message about Folio. If macOS says the app is damaged, run xattr -dr com.apple.quarantine /Applications/Folio.app in Terminal and open it again.

+
+
+
+
+ + + + diff --git a/site/llms.txt b/site/llms.txt new file mode 100644 index 0000000..9a20db4 --- /dev/null +++ b/site/llms.txt @@ -0,0 +1,24 @@ +# Folio + +> Folio is a free, open-source (MIT) desktop app for opencode, the open-source AI coding agent. It sets each opencode session as a chapter of a book: the request in handwriting, the agent's work as prose, every file read and command run as a numbered footnote, and every edit as a "plate" (a diff on the facing page) that you can accept, revise, reject or fold back. Made by Basalt Studio (https://hellobasalt.dev/). + +Platforms: macOS 12 or later (Apple silicon and Intel), Windows 10 or later, Linux (deb and AppImage, x86_64). Version 0.3.0. The installer bundles opencode 2.0.32 and starts it locally with a per-launch password; the free OpenCode Zen models need no key, other providers take a pasted key, and Folio can attach to an opencode server you already run. It does not fork or patch opencode; it uses opencode's HTTP API. + +Reject reverses that plate's own patch on disk and tells opencode the change was declined on purpose. "Ask before the press writes" makes opencode pause before each edit and shell command (Allow once or No). Six reading themes in light and dark. No account, no analytics, no telemetry. Builds are not code-signed yet, so macOS and Windows warn on first launch. + +## Pages + +- [Folio home](https://folio.hellobasalt.dev/): what it does, the catch, download and FAQ +- [Privacy](https://folio.hellobasalt.dev/privacy): Folio sends nothing to Basalt; opencode talks to the model provider you choose +- [Terms](https://folio.hellobasalt.dev/terms): MIT License, provided as is + +## Download and source + +- [Latest release](https://github.com/dpdev69/folio/releases/latest): macOS dmg, Windows installer, Linux deb and AppImage +- [Source code](https://github.com/dpdev69/folio): Tauri 2, React, TypeScript + +## Optional + +- [Basalt Studio](https://hellobasalt.dev/): the studio that makes Folio +- [opencode](https://opencode.ai/): the coding agent Folio reads +- Support: support@hellobasalt.dev diff --git a/site/privacy.html b/site/privacy.html new file mode 100644 index 0000000..113bb20 --- /dev/null +++ b/site/privacy.html @@ -0,0 +1,122 @@ + + + + + +Privacy · Folio + + + + + + + + + + + + + + + +
+
+

Privacy

+

Privacy policy

+

Last updated October 20, 2026

+

Folio has no account, no analytics and no telemetry. It sends nothing to Basalt.

+ +

Who is responsible

+

Folio is published by Basalt (hellobasalt.dev), an independent software studio. For any privacy question or request, write to support@hellobasalt.dev.

+ +

The Folio app

+
    +
  • What it runs. Folio starts a copy of opencode (the "press") on your computer, on a local address only it knows, with a password made for each launch. The window talks to it over that local connection.
  • +
  • What leaves your computer. opencode sends your messages, and the parts of your files it reads, to the AI model provider you choose (by default the free OpenCode Zen models run by the opencode team; or a provider whose key you paste). That is how any coding agent works. Those providers' own privacy terms apply. Basalt never sees this traffic.
  • +
  • What it stores. opencode keeps your sessions, any provider keys you paste, and its cache in Folio's app data folder on your computer (on macOS, ~/Library/Application Support/app.folio.desktop/press). Folio keeps its reading settings and which plates you accepted or folded in the window's local storage. None of it is uploaded.
  • +
  • Your files. Folio reads and writes files only inside the folder you open as a book, and only to show or undo the changes opencode makes.
  • +
  • Installing opencode. If you run Folio without its bundled copy and choose Install opencode, it runs the official installer from opencode.ai.
  • +
+ +

This website

+

This site is a set of static pages served by Cloudflare. It has no accounts, forms, advertising or tracking scripts, and its fonts are served from this site.

+
    +
  • Visit statistics. We use Cloudflare Web Analytics to count visits. It shows us aggregate numbers only (page views, country, browser type, referring site and load speed), sets no cookies and creates no identifier that recognises you.
  • +
  • Security cookie. If Cloudflare's security system sees unusual traffic, it may show a short check and set a security cookie called cf_clearance. It is used only to protect the site, never for ads or analytics.
  • +
  • Downloads. The app files are hosted on GitHub. GitHub may log technical details such as IP addresses to run and secure its service; see the GitHub General Privacy Statement.
  • +
+ +

Changes and questions

+

If this policy changes, the new version is published here and its history is in the repository. For questions or requests about your data, email support@hellobasalt.dev.

+
+
+ + + + diff --git a/site/robots.txt b/site/robots.txt new file mode 100644 index 0000000..5621de3 --- /dev/null +++ b/site/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://folio.hellobasalt.dev/sitemap.xml diff --git a/site/sitemap.xml b/site/sitemap.xml new file mode 100644 index 0000000..b3c1b5d --- /dev/null +++ b/site/sitemap.xml @@ -0,0 +1,6 @@ + + + https://folio.hellobasalt.dev/2026-10-20 + https://folio.hellobasalt.dev/privacy2026-10-20 + https://folio.hellobasalt.dev/terms2026-10-20 + diff --git a/site/terms.html b/site/terms.html new file mode 100644 index 0000000..a565d4b --- /dev/null +++ b/site/terms.html @@ -0,0 +1,114 @@ + + + + + +Terms · Folio + + + + + + + + + + + + + + + +
+
+

Terms

+

Terms of use

+

Last updated October 20, 2026

+

Folio is free, open-source software under the MIT License. You can use, copy, change and share it.

+ +

The license

+

Folio's source code and builds are covered by the MIT License. The opencode copy bundled with Folio is also MIT licensed; its notice is in THIRD_PARTY_NOTICES.md. The EB Garamond and Caveat fonts are under the SIL Open Font License.

+ +

Provided as is

+

As the MIT License says, Folio is provided "as is", without warranty of any kind. Folio runs an AI coding agent that can read and change files and run commands in the folder you open. Keep your work in version control, read changes before you keep them, and turn on Ask before the press writes if you want to approve each step. You are responsible for what you let it do.

+ +

AI providers

+

The models Folio talks to through opencode are run by third parties: the opencode team's OpenCode Zen, or a provider you connect with your own key. Their terms, limits and prices apply to your use of them. Free models can change or stop at any time.

+ +

Names

+

opencode is an open-source project by its own authors. Folio is an independent app and is not affiliated with or endorsed by them.

+ +

Contact

+

Questions about these terms: support@hellobasalt.dev. Bugs and ideas are welcome as a GitHub issue.

+
+
+ + + + diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 767e0c4..3b1a612 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -787,7 +787,7 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "folio" -version = "0.2.0" +version = "0.3.0" dependencies = [ "getrandom 0.3.4", "serde", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index f5ff26e..440c52d 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "folio" -version = "0.2.0" +version = "0.3.0" description = "Folio, a book-skinned desktop client for the opencode server." authors = ["Folio"] license = "MIT" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 6728b5c..cb0ee37 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Folio", - "version": "0.2.0", + "version": "0.3.0", "identifier": "app.folio.desktop", "build": { "frontendDist": "../dist", diff --git a/src/state/press.test.ts b/src/state/press.test.ts index 77ffc1c..c17e20d 100644 --- a/src/state/press.test.ts +++ b/src/state/press.test.ts @@ -12,6 +12,7 @@ import { loadBook, loadChosenModel, mapLimit, + NOT_SET_YET, NOT_TOLD, offeredModels, openChapter, @@ -126,6 +127,13 @@ describe("revertPlate", () => { ); expect(replay.requests.length).toBe(0); }); + + it("asks for a moment when the plate's turn is not named yet", async () => { + const { replay, client } = await press(); + const ch = chapter(); + await expect(revertPlate(client, book([ch]), ch, plate({ turnMessageId: "local-1760918400000" }))).rejects.toThrow(NOT_SET_YET); + expect(replay.requests.length).toBe(0); + }); }); describe("revertPlate on a new or a removed file", () => { diff --git a/src/state/press.ts b/src/state/press.ts index e09a6c7..773bfd6 100644 --- a/src/state/press.ts +++ b/src/state/press.ts @@ -382,6 +382,9 @@ export function revertNote(file: string): string { /** Said when a plate has no patch of its own to reverse. */ export const CANNOT_REVERT = "This plate cannot be set back on its own."; +/** Said when the plate's turn is still waiting for the press to name it; Reject works once it has. */ +export const NOT_SET_YET = "The press is still setting this plate. Reject it again in a moment."; + /** * Undo one plate's file: reverse the plate's own patch against the file as it * is now, write it back through Folio, and tell the model. A later change to @@ -391,7 +394,7 @@ export const CANNOT_REVERT = "This plate cannot be set back on its own."; */ export async function revertPlate(client: EngineClient, book: Book, chapter: Chapter, plate: Plate, opts: Notices = {}): Promise { // Without its own patch a plate cannot be told apart from later changes to the file. - if (!plate.patch) throw new Error(CANNOT_REVERT); + if (!plate.patch) throw new Error(isLocalId(plate.turnMessageId) ? NOT_SET_YET : CANNOT_REVERT); const created = createsFile(plate); const removed = plate.change === "deleted" || patchSides(plate.patch).newEmpty; const moved = new Error(`${plate.file} has changed since this plate was set, so it was left as it is.`); diff --git a/src/styles.css b/src/styles.css index c86194f..1374ff6 100644 --- a/src/styles.css +++ b/src/styles.css @@ -269,7 +269,10 @@ button:focus-visible, textarea:focus-visible, input:focus-visible { font-weight: 500; } .line.ink { font-size: var(--ink-fs); height: auto; line-height: 1.25; white-space: normal; } -.line.with-drop { display: flex; align-items: flex-start; gap: 8px; height: var(--lh); } +/* The cap is taller than its line and hangs over the lines beside it, so this line shows its + overflow and clips only its own words. */ +.line.with-drop { display: flex; align-items: flex-start; gap: 8px; height: var(--lh); overflow: visible; } +.line.with-drop .line-text { flex: 1 1 auto; min-width: 0; overflow: hidden; } .drop { flex: none; width: var(--drop-w);