refactor(ui): call a book a project

A book is the use case Margin was built for, not the limit of it. Blog posts published straight to
the web are coming, and "New book" on the shelf is the wrong invitation for that. Project covers
both without promising anything the app does not do.

This is user-visible strings only. Internal identifiers keep saying book, because renaming bookId
and setBooks is churn nobody reads.

Claude-Session: https://claude.ai/code/session_018HwEWvoE1NkkUjJCMLSnup
This commit is contained in:
pj committed 2026-08-31 17:26:31 +05:30
1 parent 42996804bb
commit 7eeb54f653
11 files changed
+21 -21

No files matched your search

+1 -1
View File
@@ -18,7 +18,7 @@ use tauri::{Emitter, Runtime};
fn build_menu<R: Runtime>(handle: &tauri::AppHandle<R>) -> tauri::Result<Menu<R>> {
let menu = Menu::default(handle)?;
let new_book = MenuItemBuilder::with_id("new-book", "New Book")
let new_book = MenuItemBuilder::with_id("new-book", "New Project")
.accelerator("CmdOrCtrl+N")
.build(handle)?;
let save = MenuItemBuilder::with_id("save", "Save")
+1 -1
View File
@@ -16,7 +16,7 @@ pub struct BookSummary {
fn corrupt_summary(stem: &str) -> BookSummary {
BookSummary {
id: stem.to_string(),
title: "Unreadable book".to_string(),
title: "Unreadable project".to_string(),
author: String::new(),
corrupt: true,
updated_at: 0,
+1 -1
View File
@@ -141,7 +141,7 @@ export function BackupSettings() {
disabled={working || remoteCount === 0}
onClick={() => setConfirmRestore(true)}
>
{remoteCount === null ? "Restore from Drive" : `Restore ${remoteCount} book${remoteCount === 1 ? "" : "s"}`}
{remoteCount === null ? "Restore from Drive" : `Restore ${remoteCount} project${remoteCount === 1 ? "" : "s"}`}
</button>
</div>
+1 -1
View File
@@ -72,7 +72,7 @@ export function CoverView() {
<Icon d="M4 5h16v14H4zM4 16l4.5-4.5 3 3L16 10l4 4" /> Upload image
</button>
</div>
<p className="cover-hint">Title, subtitle &amp; author are set in Book setup.</p>
<p className="cover-hint">Title, subtitle &amp; author are set in Project setup.</p>
</>
)}
<input
+2 -2
View File
@@ -285,7 +285,7 @@ export function EditorView() {
<Icon d={icons.SIDEBAR} />
</button>
</div>
<button className="doc-title" onClick={() => setSettingsOpen(true)} title="Book setup">
<button className="doc-title" onClick={() => setSettingsOpen(true)} title="Project setup">
{book.metadata.title || "Untitled"}
{dirty && <span className="dirty-dot" />}
</button>
@@ -491,7 +491,7 @@ export function EditorView() {
{escArmed && (
<div className="esc-hint">
Press <kbd>Esc</kbd> again for All books
Press <kbd>Esc</kbd> again for All projects
</div>
)}
+1 -1
View File
@@ -196,7 +196,7 @@ function ExportPreviewModal() {
<div className="preview-stage">
<div className="preview-loading">
<div className="spinner spinner-dark" />
<p>Typesetting your book…</p>
<p>Typesetting your project…</p>
</div>
</div>
)}
+1 -1
View File
@@ -275,7 +275,7 @@ export function FindBar({ editor, open, initialExpanded, onClose }: FindBarProps
title="Replace All scope"
onClick={() => setScope((s) => (s === "chapter" ? "book" : "chapter"))}
>
{scope === "chapter" ? "This chapter" : "Whole book"}
{scope === "chapter" ? "This chapter" : "Whole project"}
</button>
</div>
)}
+6 -6
View File
@@ -71,7 +71,7 @@ export function Library({ onOpen }: { onOpen: (book: Book) => void }) {
const openById = (id: string) =>
loadBook(id)
.then(onOpen)
.catch((e) => setNotice(`Could not open book: ${e}`));
.catch((e) => setNotice(`Could not open project: ${e}`));
const removeBook = async () => {
if (!pendingDelete) return;
@@ -109,7 +109,7 @@ export function Library({ onOpen }: { onOpen: (book: Book) => void }) {
<div className="shelf">
<button className="card card-action" onClick={() => createAndOpenBook(onOpen, setNotice)}>
<Icon d="M12 5v14M5 12h14" size={20} />
<span>New book</span>
<span>New project</span>
</button>
<button className="card card-action" onClick={handleImport} disabled={busy}>
<Icon d="M12 3v10m0 0l-4-4m4 4l4-4M5 19h14" size={20} />
@@ -126,7 +126,7 @@ export function Library({ onOpen }: { onOpen: (book: Book) => void }) {
<div key={b.id} className="card card-book card-corrupt">
<span className="card-title">{b.title}</span>
<span className="card-author">Couldn't be read; a .bak backup may sit beside it.</span>
<RowMenu label="Book options" className="card-menu" onDelete={() => setPendingDelete(b)} />
<RowMenu label="Project options" className="card-menu" onDelete={() => setPendingDelete(b)} />
</div>
) : (
<div
@@ -145,7 +145,7 @@ export function Library({ onOpen }: { onOpen: (book: Book) => void }) {
<span className="card-title">{b.title || "Untitled"}</span>
{b.author && <span className="card-author">{b.author}</span>}
{b.updatedAt > 0 && <span className="card-meta">Edited {relativeTime(b.updatedAt, now)}</span>}
<RowMenu label="Book options" className="card-menu" onDelete={() => setPendingDelete(b)} />
<RowMenu label="Project options" className="card-menu" onDelete={() => setPendingDelete(b)} />
</div>
),
)}
@@ -165,10 +165,10 @@ export function Library({ onOpen }: { onOpen: (book: Book) => void }) {
)}
{pendingDelete && (
<ConfirmDialog
title="Delete book"
title="Delete project"
message={
<>
Delete <strong>{pendingDelete.title || "Untitled"}</strong>? This permanently removes the book and all its
Delete <strong>{pendingDelete.title || "Untitled"}</strong>? This permanently removes the project and all its
chapters.
</>
}
+4 -4
View File
@@ -50,19 +50,19 @@ export function MoveChapterDialog({ chapter, label, onClose }: MoveChapterDialog
<div className="overlay" onClick={moving ? undefined : onClose}>
<div ref={panelRef} className="panel panel-move" onClick={(e) => e.stopPropagation()}>
<div className="panel-head">
<h2>Move to book</h2>
<h2>Move to project</h2>
<button className="icon-btn" onClick={onClose} title="Close">
<Icon d="M6 6l12 12M18 6L6 18" />
</button>
</div>
<div className="panel-body">
<p className="confirm-text">
Move <strong>{label}</strong> out of this book and into:
Move <strong>{label}</strong> out of this project and into:
</p>
{books === null ? (
<p className="move-empty">Loading books…</p>
<p className="move-empty">Loading projects…</p>
) : books.length === 0 ? (
<p className="move-empty">There is no other book to move this into yet.</p>
<p className="move-empty">There is no other project to move this into yet.</p>
) : (
<ul className="move-list">
{books.map((b) => (
+1 -1
View File
@@ -127,7 +127,7 @@ export function Settings({ onClose, onSave }: { onClose: () => void; onSave: ()
<div className="overlay" onClick={onClose}>
<div ref={panelRef} className="panel" onClick={(e) => e.stopPropagation()}>
<div className="panel-head">
<h2>Book setup</h2>
<h2>Project setup</h2>
<button className="icon-btn" onClick={onClose} title="Close">
<Icon d={icons.CLOSE} />
</button>
+2 -2
View File
@@ -169,9 +169,9 @@ export function Sidebar({ onNavigate }: { onNavigate?: () => void }) {
return (
<aside className="sidebar">
<button className="brand" onClick={closeBook} title="All books">
<button className="brand" onClick={closeBook} title="All projects">
<Icon d="M14 7l-5 5 5 5" size={15} />
<span className="back-label">All books</span>
<span className="back-label">All projects</span>
</button>
<button
className="cover-item"