Somewhere in your day is a small irritation you have learned to tolerate. Perhaps every meeting request means assembling the same details, then checking whether you forgot the duration or time zone. A small personal tool can do that repetitive assembly while leaving the decision and the wording with you.
By the end of this book, you will have a file you can open in your browser, give an input, and use to produce something useful. An app responds to input and performs an action. An attractive mockup only shows what that experience might look like. A beautiful screen with a button that does nothing is still a sketch.
The worked example is an optional booking-request planner: fill in your preferred meeting details, choose Generate request, and get editable text to copy or print. It prepares a request; it does not find available appointments or book one. If you already have a personal project, use it instead. Keep the same input → action → output method, with an example whose answer you understand.
You will ask for and revise the file in OpenCode Desktop, then use the result in a web browser. Keep those two windows distinct: the assistant's reply describes work; the browser shows whether the resulting tool helps you.
Learning objectives
After this book, you can:
- Describe one useful app through its input, action, and output, with a boundary on what it does.
- Ask a coding assistant to create one self-contained browser file and open the actual result.
- Use an editable result for your task, rather than judging only the screen's appearance.
- Request one worthwhile change and diagnose a failure with exact input, observed behaviour, and expected behaviour.
- Explain what the file retains, what it sends, and what remains your responsibility.
Core concepts
Give the app one job
Finish this sentence: “When I ___, I want to ___, so I can ___.” For the planner:
When I arrange a project discussion, I want my chosen details assembled into a request, so I can review and copy it without forgetting the time zone or duration.
That sentence sets the scope. The input is the requester's details and preferences. The action checks required fields and assembles them. The output is a plain-text request you can edit. Nothing in that flow establishes that the proposed time is available.
Here is the example we will use throughout. All personal details are synthetic:
| Input | Specimen value |
|---|---|
| Requester | Alex |
| Contact email | alex@example.test (opens in a new tab) |
| Date | 18 September 2026 |
| Time | 14:00 |
| Time zone | Europe/Berlin |
| Duration | 20 minutes |
| Purpose | project discussion |
The time means 14:00 in the explicitly supplied Europe/Berlin zone. The app should carry those two values through unchanged. It does not convert the time through UTC, take the computer's zone, or guess a zone from the email address. If the zone is missing, the input is incomplete.
Your own app may total task durations or arrange a reading queue. Describe its input, the one transformation, and the result you will use. Do not add meeting fields to a project that has no meetings. A simpler field set is fine when it still supplies everything its stated output needs.
Borrow before you build
Try the nearest existing solution once. For this example, write the meeting request in your usual plain-text editor. You can already produce the result manually. The proposed convenience is a reusable form that reminds you which details to include and assembles consistent wording.
If that text template is all you need, keeping it is a reasonable solution. Building the form is useful when you want to repeat the transformation with different inputs. Reuse the interaction that already makes sense instead of inventing a bigger system.
The catalogue example app-booking-flow offers a design brief for a booking interaction. It is inspiration for this smaller request-preparation slice, not a downloadable implementation. You do not need to install the referenced booking platform or reproduce its calendar and backend.
Understand just enough of the file
One index.html can contain three parts:
- HTML supplies the labelled fields, buttons, and output area.
- CSS makes them readable and keeps the printed request tidy.
- JavaScript reads field values and writes the request when you choose the button.
The browser already supplies useful behaviour. An email field can reject a malformed address; a required field can stop submission while empty; a number field can require a positive whole number. These are HTML constraints, not evidence that an address exists or that a meeting is available. MDN's first-form guide (opens in a new tab) explains the native building blocks.
The generated request is text. A name or purpose containing < or & should stay text, not become page instructions. Ask the assistant to use a control's value or an element's textContent when placing user-entered text in the result, rather than innerHTML.
This small app needs no model while you use it: JavaScript assembles the text in your browser. The coding assistant used to create it is a separate service and may send prompts and code to its configured model provider. Use invented, public, or explicitly approved information in that conversation.
Runnable exercise
1. Get the working place ready
You need OpenCode Desktop installed on your computer, a connected approved model that can edit a small file, a browser, and a folder where you may create practice files. Installation and model connection are prerequisites, not outcomes of this exercise.
The AI Lab portal (opens in a new tab) is the entry point for the approved application journey. A portal page in a browser is not OpenCode Desktop, and installing a launcher does not prove that Desktop and its model connection are ready. If either is missing, complete your approved setup before asking the assistant to build. This book does not require a terminal, WSL, containers, or a package installation.
In your operating system's file manager, create a new folder named request-planner-practice somewhere you can find again, such as Documents. In OpenCode Desktop, use its project/folder-opening control to open that folder. Menu wording can differ between versions; the important result is that the active project location is the folder you just created, not another project with valuable files.
In that project's conversation, identify the message area where you can ask for changes and the selected model shown by your installation. Send: “Tell me the active project folder and whether you can create a file there. Do not create anything yet.” Compare the reported path with your folder. If they differ, open the correct folder before continuing. If Desktop needs permission to work there, keep the permission scoped to this practice task.
You now have three places to recognise: Desktop for instructions, the practice folder for the actual file, and the browser for using it. The OpenCode documentation (opens in a new tab) covers asking questions and making changes. Its terminal examples and TUI image are not instructions for Desktop's exact controls.
2. Ask for the first useful version
In the Desktop conversation for your practice folder, send the following prompt. For your own project, replace the task, fields, and specimen with your input/action/output description while keeping the single-file boundary.
Create a small personal booking-request planner in this practice folder.
Create one new file named index.html with inline CSS and JavaScript.
Use no dependencies, external fonts, network calls, backend, authentication,
analytics, browser storage, or other files. Do not install anything.
Input: requester, contact email, date, time, explicit time zone, duration in
minutes, and purpose. Use native, visibly labelled fields, all required.
Use HTML constraint validation: an email input, date and time inputs,
a duration from 1 to 480 whole minutes, and nonblank text fields.
Leave the time zone empty initially; never infer it.
Action: one button named Generate request checks the inputs and assembles
one editable plain-text request in a labelled textarea. Use value or
textContent for user text, never innerHTML. Do not turn the date and time
into a UTC timestamp or convert them to the computer's time zone.
Specimen: Alex, alex@example.test, 18 September 2026, 14:00,
Europe/Berlin, 20 minutes, project discussion.
The output must include every supplied detail and say:
Status: Unsent request; availability not checked; nothing reserved.
It must not say the appointment is confirmed or that a message was sent.
Let me edit and manually copy the output. No clipboard API is needed.
If you add Print request, use the native browser print dialog and include
the full current edited request. Add no Save, Reserve, Book, or Send action.
Explain that inputs/output may reset on reload or closing: copy or print
before closing. Clear the previous generated output when a source field
changes so it cannot masquerade as the updated request; warn about this.
Keep controls keyboard-operable, focus visible, and results readable.
First briefly explain the input-to-output flow, then create the actual file.
Tell me which file you created and how to open it in my browser.
If file editing is unavailable, return the complete file contents instead.
Read the response for the promised file location and any unexpected requirements. A proposal to run a development server, create an account, connect a calendar, or install packages exceeds this first version. Ask: “Keep the same useful output, but implement it in the one local HTML file as requested.”
If the assistant only provides code, use a plain-text editor to create index.html in the practice folder. Paste the complete HTML without the surrounding Markdown backticks and save it. Check that the filename is index.html, not index.html.txt. A rich-text document is not a substitute.
3. Open the file and make a real request
In the operating system's file manager, locate the new index.html and open it with your browser. Use the operating system's choice of application if it opens in an editor. The browser should show a form; its address will normally start with file:. You are opening your file, not visiting a hosted application.
Enter the specimen from the table. A native date control may display your local date format; choose 18 September 2026, not an assumed month/day order. A time control may show a 12-hour clock; in that case enter 2:00 PM for 14:00. Enter Europe/Berlin yourself in the time-zone field.
Choose Generate request. The exact specimen request used in this book is:
Meeting request
Requester: Alex
Contact: alex@example.test
Purpose: project discussion
Requested date: 18 September 2026
Requested time: 14:00
Time zone: Europe/Berlin
Duration: 20 minutes
Status: Unsent request; availability not checked; nothing reserved.
Compare the result with what you entered. Different introductory wording can be acceptable, but every fact and the request-only status must survive. A missing time zone or the word “confirmed” is a functional error, even if the design looks excellent.
Now use the result. In the output area, add “Please let me know whether this time works.” Select the request text, copy it with your usual keyboard shortcut or context menu, and paste it into a new plain-text note. Read the pasted text once. You have produced a reusable request without sending anything; alex@example.test is a synthetic contact, not a recipient to message.
If your version includes Print request, use it and inspect the browser's print preview. The full edited text and its unsent status should appear. You can cancel the dialog. A print dialog opening does not mean a page was printed or a PDF was saved; completing that action is up to you and the available browser options.
4. Recover if no usable file was produced
If a required field is highlighted, supply the missing value and choose Generate request again. Native validation preventing an incomplete request is expected behaviour. If the file is missing, source code appears in the browser, or the button still does nothing with all fields complete, use the focused recovery guidance below.
If you are blocked by an incomplete generated file, this reference gives you a self-contained starting point. In a plain-text editor, create index.html with the code below, or replace only your disposable practice file after keeping any work you want. Then return to step 3. This is a reference implementation written for the exercise, not a captured or externally executed app. It has not been run during this manuscript revision.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Booking-request planner</title>
<style>
* { box-sizing: border-box; }
body { margin: 0; background: #0a0c0f; color: #f2f0e9;
font: 1.125rem/1.5 system-ui, sans-serif; }
main { max-width: 42rem; margin: auto; padding: 1.25rem; }
label { display: block; margin-top: 1rem; }
input, textarea, button { font: inherit; }
input, textarea { width: 100%; padding: .55rem;
color: #f2f0e9; background: #171b21; border: 1px solid #a9b1bc; }
textarea { min-height: 19rem; resize: vertical; }
button { margin-top: 1rem; padding: .6rem 1rem; cursor: pointer;
color: #0a0c0f; background: #ff6b4a; border: 2px solid transparent; }
button:disabled { opacity: .6; cursor: default; }
:focus-visible { outline: 3px solid #67e8d3; outline-offset: 3px; }
#print-text { display: none; }
@media print {
body { background: white; color: black; }
main { max-width: none; padding: 0; }
.screen { display: none; }
#print-text { display: block; white-space: pre-wrap;
overflow-wrap: anywhere; font: 12pt/1.5 sans-serif; }
}
</style>
</head>
<body>
<main>
<div class="screen">
<h1>Booking-request planner</h1>
<p>Prepare an unsent request. No availability check or reservation.</p>
<p id="lifetime">Copy or print before closing: entries may reset on reload.
Changing a source field clears the request, including your edits.</p>
<form id="planner" aria-describedby="lifetime">
<p>All fields are required. Use synthetic details for practice.</p>
<label for="requester">Requester</label>
<input id="requester" name="requester" required pattern=".*\S.*">
<label for="contact">Contact email</label>
<input id="contact" name="contact" type="email" required>
<label for="date">Requested date</label>
<input id="date" name="date" type="date" required>
<label for="time">Requested time</label>
<input id="time" name="time" type="time" step="60" required>
<label for="zone">Time zone (for example, Europe/Berlin)</label>
<input id="zone" name="zone" required pattern=".*\S.*">
<label for="duration">Duration in minutes (1–480)</label>
<input id="duration" name="duration" type="number"
min="1" max="480" step="1" required>
<label for="purpose">Purpose</label>
<input id="purpose" name="purpose" required pattern=".*\S.*">
<button type="submit">Generate request</button>
</form>
<p id="status" role="status">No request generated.</p>
<label for="request">Editable request</label>
<textarea id="request" aria-describedby="copy-help"></textarea>
<p id="copy-help">Select the request text and copy it manually.
Generate request replaces any edits in this area.</p>
<button id="print" type="button" disabled>Print request</button>
</div>
<pre id="print-text"></pre>
</main>
<script>
const form = document.getElementById('planner');
const output = document.getElementById('request');
const status = document.getElementById('status');
const printButton = document.getElementById('print');
const read = name => form.elements.namedItem(name).value.trim();
const months = ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'];
form.addEventListener('input', () => {
output.value = '';
printButton.disabled = true;
status.textContent = 'Inputs changed. Generate a new request.';
});
form.addEventListener('submit', event => {
event.preventDefault();
const [year, month, day] = read('date').split('-');
output.value = [
'Meeting request',
`Requester: ${read('requester')}`,
`Contact: ${read('contact')}`,
`Purpose: ${read('purpose')}`,
`Requested date: ${Number(day)} ${months[Number(month) - 1]} ${year}`,
`Requested time: ${read('time')}`,
`Time zone: ${read('zone')}`,
`Duration: ${read('duration')} minutes`,
'Status: Unsent request; availability not checked; nothing reserved.'
].join('\n');
printButton.disabled = false;
status.textContent = 'Request generated locally. Review, edit, then copy or print.';
output.focus();
});
output.addEventListener('input', () => {
printButton.disabled = !output.value.trim();
});
printButton.addEventListener('click', () => {
document.getElementById('print-text').textContent = output.value;
window.print();
});
</script>
</body>
</html>
You do not need to reproduce that code from memory. Ask Desktop: “Show me where this file reads inputs, handles Generate request, and writes the editable output. Explain those three places in plain English.”
In this reference, each label is connected to its field by for and id. required, the input types, and the duration limits let the browser reject incomplete or invalid inputs before the form's submit handler runs. The text-field pattern requires at least one non-whitespace character. The submit handler stops normal form submission and assembles the lines locally instead. It splits the date into written parts; it does not construct a timezone-converting timestamp. Assigning to output.value puts plain text in the editable box. The print handler copies that box's current text with textContent and opens the native dialog.
Those are the three parts of the journey you need to recognise. The rest is readable presentation, feedback, and the small rule that prevents old output from being mistaken for a new request.
5. Make one change that earns its place
Use the planner a second time with a different ordinary request. For example, keep Alex's date and time but change the duration to 30 and the purpose to project follow-up. Before changing a source field, copy any edited request you want to retain. The old output should clear; generate the new request and check that it now carries the new duration and purpose.
Notice one actual annoyance during that activity. Suppose you cannot tell which details to enter first because every label looks alike. A useful change might be to group the existing fields under “Who is requesting?” and “Requested meeting.” Another might be a larger editable output box because your added sentence is hard to see. Choose the improvement you actually need.
In Desktop, request one focused revision, for example:
In this practice folder's index.html, group the existing requester and contact
fields under Who is requesting?, and the other existing inputs under
Requested meeting. Use native fieldset and legend elements with readable
styling. Keep every label, validation rule, and input-to-output behaviour.
Add no fields, services, storage, dependencies, or files.
Tell me when the file is saved and briefly explain the change.
After the assistant saves the file, return to the browser and reload it. Re-enter your second request and perform the same activity. The grouping should help you enter the details, while the output still says 30 minutes and project follow-up. Keep the revision if it improves use. A chat message saying “updated” is not a substitute for opening and using the changed file.
6. Fix a precise failure
When something goes wrong, describe the gap rather than asking the assistant to “make it work.” For example, if your generated version permits a missing zone:
In index.html I entered Alex, alex@example.test, 18 September 2026,
14:00, 20 minutes, and project discussion, but left Time zone empty.
After Generate request, I got a completed-looking request without a zone.
Expected: the browser should identify the required Time zone field and
not generate the request until I supply it. Do not guess the zone.
Fix that validation in this file. Preserve the other fields and output.
Explain the cause and the change, then save the file.
This is a diagnostic example, not a claim that your file has that defect. Report the failure you actually saw. Include the exact input, action, result, and expected result. If Desktop asks for an error message, copy its text rather than paraphrasing it; do not paste unrelated personal data.
| What you see | Smallest useful recovery |
|---|---|
| Desktop says it created a file, but your folder has none | Ask for the exact full path. Correct the active project folder or request the complete contents and save them as index.html. |
| Opening the file shows source text | Check that you saved plain text with an .html extension, then open it with a browser rather than an editor. |
| Generate request appears to do nothing | First look for native validation at an incomplete field. If all inputs are valid and no result appears, report that exact case and ask the assistant to inspect the button's connection to the form handler. |
| Date or time changes in the output | Supply the entered and displayed values. Ask the assistant to preserve the entered date, clock time, and explicit zone without UTC conversion or timezone inference. |
| The old layout remains after an edit | Confirm the assistant saved the same file the browser has open, then reload that file. Do not start a second project to fix a stale view. |
| The request disappears after a source edit or reload | Source edits deliberately clear old output; reload is not a storage mechanism. Re-enter and regenerate. Copy or print the result before closing or making a source change. |
After a correction, repeat the case that failed. If you lost a useful change, ask Desktop to restore just that change or use the copy you kept. Avoid asking for a complete redesign when one field or handler is the problem.
Exit check
Your inspectable artifact is the actual index.html in your practice folder. Open that file and complete this short final use, with your own equivalent examples if you chose another project:
- Generate the Alex specimen and compare it with the expected request above. Edit one sentence and manually copy the complete request into a plain-text note. The time, explicit zone, duration, contact, purpose, and unsent status must survive.
- Use the second ordinary request after your worthwhile refinement. The changed values must appear, and you must be able to reach fields, activate Generate request, and edit the output using the keyboard, with visible focus.
- Try a small valid value, such as a 1-minute duration: it must remain 1 in the output. Then empty a required field, such as Purpose: generation must stop and identify what is missing. Restore it, then leave Time zone empty: generation must again stop without supplying a guessed zone. Restore
Europe/Berlinand generate successfully.
Pass when the input-to-output loop works, the result is usable outside the form, your improvement helps the task, and incomplete inputs do not masquerade as complete requests. A decorative screen, stale output, silently inferred zone, or false confirmation does not pass. Use the focused failure report to repair the particular problem; no separate testing report or extensive log is required.
Keep the file and the brief somewhere you will look next time. Retain a short opening note, for example: “Open index.html in a browser. Entries are not saved by the app; copy or print before closing.” The file is reusable; the current entries have no persistence guarantee. A browser may sometimes restore form values, but that is not a storage feature you should depend on.
When someone else needs to use the tool with you, continue to T06-L02, A tool from a prompt (opens in a new tab) for shared-app responsibilities such as access, storage, and exports.
Source notes
Primary documentation consulted for this revision on 11 September 2026:
- OpenCode documentation (opens in a new tab) identifies Desktop among the available interfaces and documents asking questions and making changes. It does not establish the exact controls visible in your installed Desktop version. No terminal screenshot is presented as Desktop.
- MDN: Your first form (opens in a new tab) explains labels, native controls, and form structure. Its server-submission examples are broader than this local-only exercise; this file assembles text without sending a form to a server.
- MDN: Window.print() (opens in a new tab) documents opening the print dialog for the current document. Printer availability, PDF destinations, and completed printing remain browser/user matters.
The owner-supplied example-led pilot brief supplies the optional app-booking-flow association and the synthetic Alex facts. That catalogue item is a design reference, not an executed app or source-code download. Its credited booking-platform reference is not evidence that this exercise implements that platform. The portal link is an entry point, not a claim that an installation was observed.
The revision also uses the supplied authoritative T06-L01 body, baseline SHA-256 44dbcdc69d658878fbf05412a69b8e16d9bbfa8423e0e7a983ab2fbf6a4ddb42, together with the shared brief, book template, editorial style, and applicable learning-version-7 guidance.
Unknowns and limitations
This planner prepares text. It does not know anyone's availability, contact a recipient, reserve a slot, authenticate a user, or store a request. An explicit zone is carried through as supplied; the simple text field does not verify that its contents name a real time zone. The user must supply a meaningful zone and review it. Native email validation checks format, not delivery or identity.
The generated version may differ from the reference. Neither this revision's reference code nor an external booking app was executed during authoring. Browser behaviour, printing, keyboard use, and the learner's generated file need the exercise's actual use check. No screenshot capture, classroom outcome, installation, or measured success rate is claimed.
The reference uses no network calls or browser storage. That is a property of the written example, not a promise about an arbitrary assistant-generated replacement, a synced folder, browser extensions, or the coding provider. Keep the practice synthetic and inspect unexpected additions before using a generated file for a real task.
How this book was made
Coordinator update, 11 September 2026: The manuscript passed its format and live-baseline checks and was accepted by an independent Terra reader. The authoring-stage account below records what was known at that earlier handoff. Learner execution and classroom outcomes remain unobserved. The supplied HTML also passed isolated Chromium checks for valid and incomplete inputs, editable output, safe text handling and print-content layout; actual Desktop generation and operating-system printing were not tested.
The owner assigned this example-led revision to an isolated T06-L01 writing context and explicitly authorised Astra for the three-book pilot. The runtime identity exposed to this author was OpenAI openai/gpt-6-astra. Astra wrote this revision and its reference implementation on 11 September 2026; no additional agents or independent reviewers participated in this authoring context.
The earlier T06-L01 manuscript supplied the useful-personal-tool opening, the working-app versus mockup distinction, input/action/output, borrow-before-build reasoning, the local-browser route, learner choice, focused failure diagnosis, and improvement through actual use. Those are retained or developed from existing material, not claimed as wholly new Astra ideas. The supplied body contains no named original-author credit; this revision does not infer one or replace any existing catalogue or historical contributor attribution, including any Sol credit held there.
New work connects those lessons through one optional request planner: Desktop/folder/browser orientation, a bounded build prompt, exact synthetic inputs and expected output, the complete fallback HTML, one use-driven refinement, and recovery and exit steps tied to that same task. The practical lesson is that “build an app” becomes easier to judge when you can follow one real input all the way into text you actually use.
The handoff is this draft Markdown file for coordinator integration and the single consolidated verification after the pilot edits are combined. This author did not run tests, lint, typechecking, browser checks, or manuscript validation, and did not publish the book. Independent review and execution results are pending. Per-phase token counts, cache/reasoning counters, measured cost, exact timing, and complete tool-call telemetry were not supplied as an authoritative telemetry record; they remain unknown, not zero.