=== BEGIN ARTIFACT ARCHITECTURE.md === # ARCHITECTURE: ReadingList | Field | Value | |-------------|-------| | Version | 20260815 V1 | | Description | Defines the Flask application structure and module boundaries for the ReadingList web application. | | Depends On | — | | Provides | application_factory, web_entrypoint | | Consumes | — | ## Questions - None. ## Intent ReadingList is a small Flask web application for maintaining an ordered personal list of books to read. The application uses one reader-facing workflow: submit a book, review the ordered list, and remove a book. ## Modules and Boundaries | Module | Responsibility | |---|---| | `app/__init__.py` | Application factory and configuration initialization. | | `app/routes.py` | HTTP route registration and request/response orchestration. | | `app/persistence.py` | SQLite connection management and the typed book-store boundary. | | `app/templates/` | Reader-facing HTML templates. | | `app/static/` | Shared CSS presentation. | | `tests/` | Automated behavior tests. | | `bin/test.sh` | POSIX-compatible complete-suite launcher. | The application factory creates an isolated Flask application, configures the database location, initializes the persistence boundary, and registers routes. Route handlers do not execute raw SQL. Persistence code does not render templates or make HTTP decisions. ## Technical Decisions - Use a Flask application factory so tests can create isolated application instances. - Use SQLite for local persistence. - Preserve insertion order with an integer primary key and an ordered query. - Keep the initial workflow on one screen at `/`. - Use redirects after successful form mutations so the list is read again from persistence. ## Technology Stack | Technology | Use | |---|---| | Python | Application and test implementation. | | Flask | Web application, routing, templates, and test client. | | SQLite | Local persistent storage for books. | | pytest | Automated behavior test runner. | | HTML/CSS | Reader-facing interface and shared presentation. | ## Module Ownership | Boundary | Owning module | Allowed low-level access | |---|---|---| | Application configuration | Application factory | Flask configuration APIs and environment values. | | Persistence | `app.persistence` | SQLite connections and SQL statements. | | HTTP | `app.routes` | Flask request, response, redirect, and rendering APIs. | | Presentation | Templates and static assets | HTML, template variables, and CSS only. | | Verification | `tests/` and `bin/test.sh` | Test and process execution APIs. | ## Programmatic Acceptance === AC architecture-factory === Intent: The application factory creates a runnable Flask application with an HTTP test client. from app import create_app application = create_app({"TESTING": True, "DATABASE": ":memory:"}) client = application.test_client() response = client.get("/") assert response.status_code == 200 === END AC architecture-factory === === AC architecture-isolation === Intent: Separate application instances can be created independently for isolated execution. from app import create_app first = create_app({"TESTING": True, "DATABASE": ":memory:"}) second = create_app({"TESTING": True, "DATABASE": ":memory:"}) assert first is not second assert first.test_client().get("/").status_code == 200 assert second.test_client().get("/").status_code == 200 === END AC architecture-isolation === === AC architecture-entrypoint === Intent: The web entrypoint exposes the application factory without requiring a development server to start during import. import importlib module = importlib.import_module("app") assert callable(module.create_app) === END AC architecture-entrypoint === ## User Acceptance - None. ## Guardrails - Route handlers must not access SQLite directly. - Persistence code must not reorder books. - The application must remain runnable as a web application. === END ARTIFACT === === BEGIN ARTIFACT DATABASE.md === # DATABASE: Book Persistence | Field | Value | |-------------|-------| | Version | 20260815 V1 | | Description | Defines the SQLite persistence contract for ordered reading-list books. | | Depends On | ARCHITECTURE.md | | Provides | book_store.add, book_store.list_ordered, book_store.remove, books_table | | Consumes | application_factory | ## Questions - None. ## Access Patterns | Caller | Operation | Store | Interface | |---|---|---|---| | Book creation workflow | Add a submitted title and author | `books` | `BookStore.add(title, author)` | | Ordered-list workflow | Read all books | `books` | `BookStore.list_ordered()` | | Book-removal workflow | Delete a selected book | `books` | `BookStore.remove(book_id)` | ## Persistence Interfaces | Store | Public interface | Module | Allowed callers | Notes | |---|---|---|---|---| | SQLite `books` table | `BookStore.add(title, author) -> Book` | `app.persistence` | Application workflows | Persists one book. | | SQLite `books` table | `BookStore.list_ordered() -> list[Book]` | `app.persistence` | Application workflows | Returns rows in insertion order. | | SQLite `books` table | `BookStore.remove(book_id) -> bool` | `app.persistence` | Application workflows | Removes the selected row and reports whether it existed. | ## Schema The `books` table contains: - `id`: integer primary key. - `title`: required text. - `author`: required text. - `created_at`: persisted timestamp or equivalent insertion marker. The primary key is monotonically assigned by SQLite and is used to preserve insertion order. The database must reject null title or author values. Application validation additionally rejects empty submitted values. ## Configuration The application factory supplies the database location through Flask configuration. Tests may provide an isolated temporary path or an in-memory database. Connections are scoped to the application context and closed after use. ## Migrations and Initialization Application startup creates the `books` table when it does not exist. Initialization is idempotent and must not delete existing rows. ## Programmatic Acceptance === AC database-add-readback === Intent: A book added through the persistence interface can be read back with the submitted fields. from app import create_app from app.persistence import get_book_store title = "The Dispossessed" author = "Ursula K. Le Guin" application = create_app({"TESTING": True, "DATABASE": ":memory:"}) with application.app_context(): store = get_book_store() created = store.add(title, author) books = store.list_ordered() assert len(books) == 1 assert books[0].id == created.id assert books[0].title == title assert books[0].author == author === END AC database-add-readback === === AC database-order === Intent: Ordered reads preserve the order in which books were added. from app import create_app from app.persistence import get_book_store first_title = "A" first_author = "Author A" second_title = "B" second_author = "Author B" application = create_app({"TESTING": True, "DATABASE": ":memory:"}) with application.app_context(): store = get_book_store() store.add(first_title, first_author) store.add(second_title, second_author) books = store.list_ordered() assert [book.title for book in books] == [first_title, second_title] assert [book.author for book in books] == [first_author, second_author] === END AC database-order === === AC database-remove === Intent: Removing an existing book makes it absent from a subsequent persistence read. from app import create_app from app.persistence import get_book_store title = "To Remove" author = "Author" application = create_app({"TESTING": True, "DATABASE": ":memory:"}) with application.app_context(): store = get_book_store() created = store.add(title, author) removed = store.remove(created.id) books = store.list_ordered() assert removed is True assert books == [] === END AC database-remove === === AC database-empty === Intent: A new database returns an empty ordered collection. from app import create_app from app.persistence import get_book_store application = create_app({"TESTING": True, "DATABASE": ":memory:"}) with application.app_context(): books = get_book_store().list_ordered() assert books == [] === END AC database-empty === ## User Acceptance - None. ## Guardrails - All SQLite access is encapsulated by `app.persistence`. - Empty titles and authors must never be stored. - Ordered reads must preserve addition order. === END ARTIFACT === === BEGIN ARTIFACT UI-GENERAL.md === # UI-GENERAL: Reading List Presentation | Field | Value | |-------------|-------| | Version | 20260815 V1 | | Description | Defines shared accessible presentation patterns for the single-screen reading-list interface. | | Depends On | ARCHITECTURE.md | | Provides | reading_list_ui_patterns | | Consumes | application_factory | ## Questions - None. ## Presentation Patterns The reading-list screen uses a simple responsive page with: - A clear page heading identifying the reading list. - A book-submission form containing required title and author inputs. - A submit control with an understandable action label. - An ordered list of books, showing each title with its author. - A removal control associated with each listed book. - A clear empty-list message when no books exist. - A visible validation-error region when a submission is rejected. Controls must have labels, inputs must have stable names, and each removal control must identify its target book without relying on position alone. Error presentation must identify the missing required field or fields. ## CSS Patterns Styles should provide readable typography, visible focus states, sufficient contrast, and usable spacing on narrow and wide screens. Styling must not change the order of books or hide validation errors. ## Programmatic Acceptance === AC ui-patterns-form === Intent: The shared presentation includes labeled title and author controls and a submission control. from app import create_app application = create_app({"TESTING": True, "DATABASE": ":memory:"}) response = application.test_client().get("/") body = response.get_data(as_text=True) assert response.status_code == 200 assert 'name="title"' in body assert 'name="author"' in body assert "