{"ns":"inn","title":"Willow Hollow B&B","version":"1.8","summary":"Willow Hollow B&B is a guesthouse website. Visitors browse rooms, choose a stay by dragging across a month calendar, book with their details and send the owner a message; the owner signs in to an admin panel to manage rooms, see bookings on a report calendar, read messages and change the branding shown publicly.","problem":"A small guesthouse needs bookings that can never double-book a room, honest validation for guests and a simple back office whose changes show up on the public page immediately.","personas":[{"name":"Guest","need":"Pick dates, see the price and book a free room in one pass; ask the owner a question."},{"name":"Owner / admin","need":"Keep rooms, bookings, messages and branding up to date."}],"goals":["A room is never booked twice for the same night, yet a stay may begin on the day the previous guest leaves.","Every invalid input is reported per field, before anything is stored.","Admin changes (rooms, branding, deletions) are reflected everywhere they are used."],"features":[{"id":"FRT","title":"Front page","summary":"Branding, rooms and room features.","reqs":[{"id":"FR-FRT-01","weight":"Must","text":"The front page shows the guesthouse name and description and every room in room-number order with its price per night.","accept":["Heading \"Willow Hollow B&B\" with the \"family-run bed and breakfast\" description","Five cards: Single $95, Double $150, Suite $225, Twin $120, Family $190, each \"per night\"","The accessible (wheelchair) icon appears on the Double and Family cards only"]},{"id":"FR-FRT-02","weight":"Could","text":"Each room card shows a badge for exactly the features configured for the room.","accept":["Suite: WiFi, TV, Radio, Refreshments, Safe, Views","Twin: WiFi, Views"]}]},{"id":"BKG","title":"Booking","summary":"Drag-to-select stay dates and guest details.","reqs":[{"id":"FR-BKG-01","weight":"Must","text":"A stay is chosen by pressing on the check-in day and dragging to the check-out day; the summary shows the dates, the number of nights and the total price (nights × nightly price).","accept":["Twin, 3 → 6 May 2027: \"Check-in Mon 3 May 2027 · Check-out Thu 6 May 2027 · 3 nights · $360 total\"","Single, 13 → 15 March 2027: \"2 nights · $190 total\""]},{"id":"FR-BKG-02","weight":"Must","text":"A valid guest books the stay and receives a confirmation; the nights become Unavailable in the calendar and the check-out day stays free.","accept":["Dialog \"Booking Successful!\" — \"Your booking has been confirmed for: 2027-05-03 - 2027-05-06\"","Reopened calendar: days 3, 4 and 5 Unavailable, day 6 free"]},{"id":"FR-BKG-03","weight":"Must","text":"Existing bookings show their nights as Unavailable; the check-out day is not a booked night.","accept":["Single, March 2027: days 10, 11, 12, 20 and 21 Unavailable; days 13 and 22 are not"]},{"id":"FR-BKG-04","weight":"Must","text":"A stay overlapping any booked night of the room — including the booking’s last night — is refused and nothing is stored.","accept":["Single, 11 → 14 March and 12 → 14 March 2027: error \"The room dates are either invalid or are already booked\"; no success dialog"]},{"id":"FR-BKG-05","weight":"Should","text":"A stay may begin on the day a previous guest checks out.","accept":["Single, 13 → 15 March 2027 books successfully for 2027-03-13 - 2027-03-15"]},{"id":"FR-BKG-06","weight":"Should","text":"A single-day click selects zero nights and cannot be booked.","accept":["Summary \"No nights selected — drag from check-in day to check-out day\"","Booking shows \"Drag across the calendar to choose at least one night\""]},{"id":"FR-BKG-07","weight":"Should","text":"Stays starting before today are refused with the same dates error.","accept":["Dragging 10 → 12 in the previous month: days greyed as past, \"2 nights\"; Book shows \"The room dates are either invalid or are already booked\""]},{"id":"FR-BKG-08","weight":"Should","text":"Guest fields are required and length-checked, with one message per invalid field.","accept":["Blank: \"Firstname must not be blank\", \"Lastname must not be blank\", \"Email must not be blank\", \"Phone must not be blank\"","Firstname 3–18, lastname 3–30 and phone 11–21 characters; \"Al\" and a 10-digit phone give \"Firstname must be between 3 and 18 characters.\" and \"Phone must be between 11 and 21 characters.\"","\"Bea\" with an 11-digit phone books successfully"]},{"id":"FR-BKG-09","weight":"Could","text":"Cancel closes the booking panel on the card without booking and restores \"Book this room\"."}]},{"id":"CNT","title":"Contact form","summary":"Messages from guests to the owner.","reqs":[{"id":"FR-CNT-01","weight":"Must","text":"A valid message thanks the sender by name and echoes the subject.","accept":["\"Thanks for getting in touch Nora Vance!\" then \"We'll get back to you about Parking for two cars as soon as possible.\""]},{"id":"FR-CNT-02","weight":"Should","text":"An empty form lists all five required-field errors at once.","accept":["\"Name may not be blank\", \"Email may not be blank\", \"Phone may not be blank\", \"Subject may not be blank\", \"Message may not be blank\""]},{"id":"FR-CNT-03","weight":"Should","text":"Subject must be 5–100 characters and message 20–2000.","accept":["\"Help\" and a 19-character message give \"Subject must be between 5 and 100 characters.\" and \"Message must be between 20 and 2000 characters.\"","\"Help!\" and a 20-character message are accepted"]},{"id":"FR-CNT-04","weight":"Must","text":"A submitted message arrives in the admin inbox as unread and raises the unread badge.","accept":["After one new message the badge shows 3 and \"Nora Vance — Parking for two cars\" is the first, bold row"]}]},{"id":"ADM","title":"Admin sign-in","summary":"Access control for the back office.","reqs":[{"id":"FR-ADM-01","weight":"Must","text":"The admin signs in with valid credentials; wrong credentials are refused and the fields outlined.","accept":["Wrong credentials: \"Invalid credentials\" and both fields outlined red","Success: navbar Rooms, Report, Branding, Messages (unread badge), Front Page, Logout; rooms 101–105 listed"]},{"id":"FR-ADM-02","weight":"Should","text":"Admin pages require a session: logged-out visits land on the login form and Logout ends the session.","accept":["Opening #/admin/report signed out redirects to #/admin with \"Log into your account\"","After Logout, #/admin/messages shows the login form"]}]},{"id":"ROM","title":"Room management","summary":"Create, edit and delete rooms.","reqs":[{"id":"FR-ROM-01","weight":"Must","text":"A created room appears in the admin list and on the front page with its price and feature badges.","accept":["Room 106, Double, not accessible, price 140, WiFi + TV: the list row reads \"106 · Double · false · 140 · WiFi, TV\"; the front page has a sixth card Double $140"]},{"id":"FR-ROM-02","weight":"Should","text":"Room creation is validated with itemised messages: room number required and unique, price a whole number from 1 to 999.","accept":["Empty: \"Room name must be set\" and \"must be greater than or equal to 1\"","Duplicate 101: \"Room 101 already exists\"","Price 1000: \"must be less than or equal to 999\"; 12.5: \"Price must be a whole number\""]},{"id":"FR-ROM-03","weight":"Must","text":"Both price limits are valid: 1 and 999 save.","accept":["Rooms 201 (price 1) and 202 (price 999) are created"]},{"id":"FR-ROM-04","weight":"Could","text":"Editing a room updates the admin view and the front page.","accept":["Room 104 set to price 130 with TV added: details \"WiFi, TV, Views\"; the Twin card shows $130 and WiFi, TV, Views badges"]},{"id":"FR-ROM-05","weight":"Must","text":"Deleting a room removes it and all its bookings everywhere, including the report calendar.","accept":["Deleting room 103 leaves four rooms and no \"Tomas Lindqvist\" bar on 2–5 April 2027"]}]},{"id":"RPT","title":"Report and booking edits","summary":"Owner’s calendar of all bookings.","reqs":[{"id":"FR-RPT-01","weight":"Should","text":"The report calendar draws each booking as a bar across its nights labelled with the guest name; clicking a bar shows the booking.","accept":["March 2027: \"Jules Ferro\" 10–12, \"Priya Nandakumar\" 12–14, \"Ada Okafor\" 20–21","Jules Ferro dialog: Room 101, \"2027-03-10 - 2027-03-13 (3 nights)\", Deposit paid: true"]},{"id":"FR-RPT-02","weight":"Should","text":"A booking can be edited from its room page, but an edit that overlaps another booking is refused and the booking is unchanged.","accept":["Priya’s check-out changed to 2027-03-16 is saved","Jules Ferro’s check-out changed to 2027-03-21 shows \"The room dates are either invalid or are already booked\"; it stays 2027-03-10 → 2027-03-13"]},{"id":"FR-RPT-03","weight":"Should","text":"Deleting a booking removes it from the room page and the report and frees its nights for guests.","accept":["After deleting Ada Okafor’s booking only Jules Ferro remains for room 101 and days 20 and 21 are no longer Unavailable"]}]},{"id":"MSG","title":"Messages inbox","summary":"Reading and clearing guest messages.","reqs":[{"id":"FR-MSG-01","weight":"Must","text":"The inbox lists messages newest first with unread ones in bold; the navbar badge equals the number of unread messages and drops when a message is opened.","accept":["Seed: Rafael Costa, Mei Tanaka, Hollis Grant; Rafael and Hollis bold; badge 2","Opening Rafael Costa shows phone 07700900777, email and message; after closing it is no longer bold and the badge reads 1"]},{"id":"FR-MSG-02","weight":"Should","text":"Deleting a message removes its row and updates the badge.","accept":["With an unread new message the badge goes from 3 to 2 after deleting it"]}]},{"id":"BRN","title":"Branding","summary":"Public name and contact details.","reqs":[{"id":"FR-BRN-01","weight":"Must","text":"Saved branding replaces the public name and contact details on the front page.","accept":["Dialog \"Branding updated!\"","Name \"Willow Hollow Guesthouse\" becomes the front-page heading; contact phone 01234500000 appears on the contact card"]},{"id":"FR-BRN-02","weight":"Could","text":"Invalid branding is refused with one message per field and no success dialog.","accept":["Name \"AB\": \"Name must be between 3 and 100 characters.\"","Latitude 95: \"Latitude must be a number between -90 and 90\"","Email \"stay-at-home\": \"must be a well-formed email address\""]}]},{"id":"API","title":"Booking and admin API","summary":"REST access to rooms, bookings and reports.","reqs":[{"id":"FR-API-01","weight":"Should","text":"Public endpoints (rooms, bookings) are open; admin endpoints require a bearer token; overlaps are rejected with 409.","accept":["GET /api/inn/rooms: 200 with five rooms","GET /api/inn/report without a token: 401","With a token: 200 with four bookings ordered by check-in","POST /api/inn/bookings for room 1, 2027-03-11 → 2027-03-12: 409 \"The room dates are either invalid or are already booked\""]}]}],"nfr":[{"id":"NFR-01","weight":"Must","text":"Security: admin pages and admin API calls are inaccessible without a signed-in session or token."},{"id":"NFR-02","weight":"Should","text":"Data format: dates are shown as YYYY-MM-DD in confirmations and reports; prices are whole dollar amounts."}],"glossary":[{"term":"Night","meaning":"A day on which a guest sleeps in the room; the check-out day is not a night."},{"term":"Unavailable","meaning":"Calendar state of a night that is already booked."}],"outOfScope":["Payments and deposits","Email notifications","Map rendering"]}