[Spec Kit] Initial commit — constitution, spec, plan, and tasks for Reaction Image Board v1
This commit is contained in:
259
specs/001-reaction-image-board/spec.md
Normal file
259
specs/001-reaction-image-board/spec.md
Normal file
@@ -0,0 +1,259 @@
|
||||
# Feature Specification: Reaction Image Board v1
|
||||
|
||||
**Feature Branch**: `001-reaction-image-board`
|
||||
**Created**: 2026-05-02
|
||||
**Status**: Draft
|
||||
**Input**: User description: "Read docs/SPEC.md, from which we will create the official spec"
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 — Upload an Image (Priority: P1)
|
||||
|
||||
A user drags and drops (or browses to select) a single image file from their
|
||||
device, optionally adds tags, and submits the upload. The image appears in
|
||||
the library immediately. If the same image was already uploaded before, the
|
||||
system recognises the duplicate and shows the existing entry without creating
|
||||
a second copy.
|
||||
|
||||
**Why this priority**: This is the core data-entry action. Without it no
|
||||
library content exists to browse, search, or manage.
|
||||
|
||||
**Independent Test**: Upload a JPEG, verify it appears in the library grid,
|
||||
then re-upload the same file and verify only one copy exists with an
|
||||
"Already in your library" notification.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a supported image file (JPEG, PNG, GIF, or WebP) under 50 MB,
|
||||
**When** the user submits the upload form,
|
||||
**Then** the image is stored, appears in the library, and the user is
|
||||
taken to the image's detail page.
|
||||
|
||||
2. **Given** an image already in the library,
|
||||
**When** the user uploads the same file again,
|
||||
**Then** no duplicate is stored, the user sees an "Already in your library"
|
||||
notification, and is navigated to the existing image's detail page.
|
||||
|
||||
3. **Given** an unsupported file type (e.g. PDF, MP4),
|
||||
**When** the user attempts to upload it,
|
||||
**Then** an inline error is shown and the user remains on the upload page.
|
||||
|
||||
4. **Given** a file larger than 50 MB,
|
||||
**When** the user attempts to upload it,
|
||||
**Then** an inline error is shown before any storage is attempted.
|
||||
|
||||
5. **Given** a tag name longer than 64 characters or containing characters
|
||||
outside lowercase letters, digits, hyphens, and underscores,
|
||||
**When** the user submits the upload,
|
||||
**Then** an inline validation error identifies the problematic tag and the
|
||||
upload does not proceed.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 — Browse and Filter the Library (Priority: P1)
|
||||
|
||||
A user opens the application and sees a grid of all their uploaded images as
|
||||
thumbnails. They can filter the grid by selecting one or more tags; the grid
|
||||
updates to show only images that carry **all** selected tags. Filters can be
|
||||
added and removed interactively without reloading the page.
|
||||
|
||||
**Why this priority**: The library view is the default landing page and the
|
||||
primary way to find and re-use reaction images.
|
||||
|
||||
**Independent Test**: Seed the library with tagged images, apply a single tag
|
||||
filter and verify only matching images are shown, then add a second filter and
|
||||
verify both tags are required on every visible result.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** the library contains images,
|
||||
**When** the user opens the application,
|
||||
**Then** all images are shown in reverse chronological order as thumbnails
|
||||
with their tags displayed beneath each one.
|
||||
|
||||
2. **Given** a non-empty library,
|
||||
**When** the user selects one or more tags in the filter bar,
|
||||
**Then** only images that have every selected tag are shown.
|
||||
|
||||
3. **Given** active tag filters,
|
||||
**When** the user removes a filter chip,
|
||||
**Then** the grid expands to reflect the remaining filters (or shows all
|
||||
images if no filters remain).
|
||||
|
||||
4. **Given** a large library (more images than fit on screen),
|
||||
**When** the user scrolls to the bottom or clicks "Load more",
|
||||
**Then** additional images load without replacing the already-visible ones.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 — View Image Detail and Edit Tags (Priority: P2)
|
||||
|
||||
A user clicks an image in the library to open a detail page showing the
|
||||
full-size image and its current tags. They can add new tags or remove
|
||||
existing ones. Changes are saved on blur or pressing Enter, not on every
|
||||
keystroke.
|
||||
|
||||
**Why this priority**: Tag management is the primary organisation mechanism;
|
||||
editing must be accessible from the image itself.
|
||||
|
||||
**Independent Test**: Open any image detail page, add a new tag, navigate
|
||||
back to the library, filter by that tag, and confirm the image appears.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** the user navigates to an image detail page,
|
||||
**When** the page loads,
|
||||
**Then** the full-size image is displayed alongside all its current tags.
|
||||
|
||||
2. **Given** the user types a new tag into the tag input and presses Enter
|
||||
(or moves focus away),
|
||||
**Then** the tag is added to the image and the display updates immediately.
|
||||
|
||||
3. **Given** the user clicks the remove (×) button on an existing tag chip,
|
||||
**Then** the tag is removed from the image.
|
||||
|
||||
4. **Given** a tag value that exceeds 64 characters or contains invalid
|
||||
characters,
|
||||
**When** the user tries to save it,
|
||||
**Then** an inline error is shown and the invalid tag is not persisted.
|
||||
|
||||
---
|
||||
|
||||
### User Story 4 — Delete an Image (Priority: P2)
|
||||
|
||||
A user chooses to permanently remove an image from their library. A
|
||||
confirmation step prevents accidental deletion. After deletion, the image
|
||||
is gone from the library view and from storage.
|
||||
|
||||
**Why this priority**: Users must be able to remove unwanted content from a
|
||||
personal collection.
|
||||
|
||||
**Independent Test**: Delete a known image, confirm it no longer appears in
|
||||
the library, and confirm that navigating to its former detail URL shows a
|
||||
"not found" screen.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** the user is on an image detail page,
|
||||
**When** they click the delete button and confirm,
|
||||
**Then** the image and its stored file are permanently removed and the user
|
||||
is returned to the library.
|
||||
|
||||
2. **Given** the user clicks the delete button,
|
||||
**When** they dismiss the confirmation dialog (cancel),
|
||||
**Then** no deletion occurs and the user remains on the detail page.
|
||||
|
||||
---
|
||||
|
||||
### User Story 5 — Browse and Search Tags (Priority: P3)
|
||||
|
||||
A user can view a list of all tags currently in use, along with how many
|
||||
images each tag is applied to. They can type a prefix to narrow the list.
|
||||
|
||||
**Why this priority**: Useful for discovering existing tags and maintaining a
|
||||
consistent vocabulary, but the library filter bar already enables tag selection
|
||||
so this is supplementary.
|
||||
|
||||
**Independent Test**: Open the tag browser and verify every tag present in the
|
||||
library appears with a correct image count, then type a prefix and verify only
|
||||
matching tags remain visible.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** the user opens the tag browser,
|
||||
**When** the page loads,
|
||||
**Then** all tags are listed alphabetically with their image counts.
|
||||
|
||||
2. **Given** the user types a prefix into the search input,
|
||||
**Then** only tags whose names begin with that prefix are shown.
|
||||
|
||||
---
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- What happens when the library is empty? → An empty-state prompt is shown
|
||||
encouraging the user to upload their first image.
|
||||
- What happens when a tag filter matches zero images? → The grid shows an
|
||||
empty-results message (not an error).
|
||||
- What happens when the user navigates to a non-existent image ID? → A "Not
|
||||
found" screen is shown with a link back to the library.
|
||||
- What happens when the user navigates to an unknown route? → A "Not found"
|
||||
screen is shown.
|
||||
- What happens when the upload form is submitted with no tags? → The image is
|
||||
stored with no tags; no validation error is raised.
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: System MUST accept uploads of JPEG, PNG, GIF, and WebP images
|
||||
up to 50 MB per file; all other types MUST be rejected.
|
||||
- **FR-002**: System MUST detect duplicate image content at upload time and
|
||||
return the existing record without writing a duplicate to storage.
|
||||
- **FR-003**: Users MUST be able to attach zero or more tags to an image at
|
||||
upload time via a comma-or-space-separated text input.
|
||||
- **FR-004**: Tag names MUST be normalised (lowercased, whitespace-trimmed)
|
||||
before storage and MUST conform to: lowercase letters, digits, hyphens, and
|
||||
underscores only, 1–64 characters.
|
||||
- **FR-005**: Users MUST be able to filter the image library by one or more
|
||||
tags; the filter logic MUST be AND (every specified tag must be present on
|
||||
the result).
|
||||
- **FR-006**: All list views MUST support pagination; no view may load the
|
||||
entire library at once.
|
||||
- **FR-007**: Users MUST be able to replace the complete tag set on an
|
||||
existing image (add new tags, remove existing tags) from the detail view.
|
||||
- **FR-008**: Users MUST be able to permanently delete an image including its
|
||||
stored file and all tag associations, after a confirmation step.
|
||||
- **FR-009**: Images MUST be viewable in the browser (thumbnail in library,
|
||||
full-size on detail page) without exposing permanent internal storage
|
||||
credentials or addresses.
|
||||
- **FR-010**: Users MUST be able to list all tags sorted alphabetically with
|
||||
associated image counts, with optional prefix filtering.
|
||||
- **FR-011**: Tags MUST be created implicitly on first use; no explicit
|
||||
tag-creation step is required.
|
||||
- **FR-012**: Removing a tag from an image MUST NOT delete the shared tag
|
||||
record or affect other images that use the same tag.
|
||||
|
||||
### Key Entities *(include if feature involves data)*
|
||||
|
||||
- **Image**: A single uploaded file. Key attributes: unique content
|
||||
fingerprint, original filename, file type, pixel dimensions, file size,
|
||||
upload timestamp, associated tags.
|
||||
- **Tag**: A normalised text label that can be applied to many images.
|
||||
Key attributes: name (unique, always lowercase), creation timestamp, count
|
||||
of images currently using it.
|
||||
- **ImageTag**: The many-to-many association between an image and a tag.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: A user can upload an image with tags and see it appear in the
|
||||
library in under 10 seconds on a local network connection.
|
||||
- **SC-002**: Re-uploading an identical image produces no duplicate library
|
||||
entry; duplicate detection is invisible to the user except for the
|
||||
informational notification.
|
||||
- **SC-003**: The library's first page of results loads in under 2 seconds
|
||||
for a collection of 1,000 images.
|
||||
- **SC-004**: A tag-filtered search with 1–3 active tags returns results in
|
||||
under 2 seconds across a library of 1,000 images.
|
||||
- **SC-005**: A user can add or remove a tag on an existing image within
|
||||
5 seconds of interaction on a local network connection.
|
||||
- **SC-006**: The complete application starts from a clean checkout with a
|
||||
single command and requires no manual setup beyond copying the example
|
||||
environment file.
|
||||
|
||||
## Assumptions
|
||||
|
||||
- The application serves a single user (the owner) on a local network.
|
||||
No authentication, access control, or multi-user isolation is required in v1.
|
||||
- Only one image file can be submitted per upload action; bulk upload is out
|
||||
of scope for v1.
|
||||
- Images are immutable after upload: file content is never replaced; only the
|
||||
tag associations may change.
|
||||
- The deployment environment provides S3-compatible object storage (locally
|
||||
via MinIO for development).
|
||||
- Target clients are modern evergreen desktop browsers; mobile-native
|
||||
experience is explicitly out of scope for v1.
|
||||
- OR/NOT tag logic, collections/albums, image editing, alternative sort
|
||||
orders, and multi-user features are all explicitly out of scope for v1.
|
||||
Reference in New Issue
Block a user