Language: 한국어 · English
If data disappears after a refresh, do not start by rewriting the save function. First identify where the data existed. Seeing a value on screen does not prove it was persisted in browser storage or written to a server database.
This Vibe Coding Clinic answers one question: “A note appears after I add it, but vanishes on refresh. What should I inspect first, and what is the smallest valid fix?”
1. Symptom: adding works, refreshing erases it
You enter a note and click Add. It appears in the list. There is no obvious error. After a refresh, the list is empty again. The first question is not “Did the button work?” It is “How far did the new value travel?”
- UI state: the value exists only in JavaScript memory for the current page
- Browser storage: the value exists in localStorage, IndexedDB, or another store for the same origin
- Server persistence: a network request wrote the value through a backend to a database
A refresh starts the first layer again. If the app never writes to and reads from the second or third layer, losing the note is expected behavior.
2. Separate the scope: UI, browser, or server
A mobile-friendly diagnostic flow
- Does the note appear before refresh?
- Does it disappear after refresh?
- Is there a storage key under DevTools → Application → Local Storage?
- Does the Network panel show a POST, PUT, or PATCH request?
- Does the Console or the page show a storage error?
If steps 3 and 4 both produce nothing, this is not yet a database outage. The app has no observed path out of in-memory UI state.
3. Minimal reproduction: a note that lives only in memory
This code adds text to an array and renders the list. It looks successful until the page reloads and notes is initialized as an empty array again.
let notes = [];
form.addEventListener('submit', event => {
event.preventDefault();
const text = input.value.trim();
if (!text) return;
notes = [...notes, text];
render();
});
Reproduce it: add a note → see it in the list → refresh → see an empty list. The click handler worked. Persistence was never implemented.
4. Diagnose in the order evidence should appear
① Locate the write path in code
If you only find useState, a plain array, or a global variable—and no localStorage.setItem, IndexedDB call, fetch, or server SDK call—the app does not have durable persistence yet.
② Inspect browser storage directly
Open DevTools → Application → Local Storage and select the page’s current origin. No key means the write did not run or failed. A key with an empty UI points instead to reading, JSON parsing, or initial rendering.
③ Inspect the Network panel
If you expected server persistence, watch for a request after the click. No request means the first missing link is still in the frontend. A request that returns a 4xx or 5xx response moves the investigation to validation, authentication, backend, or database behavior.
④ Compare origins
localStorage is partitioned by origin. http://localhost:8000 and http://localhost:3000 do not share it; HTTP and HTTPS do not share it either. The standard does not define dependable localStorage behavior for a page opened directly with a file: URL, so do not use that as the test condition for this lesson.
5. The smallest fix: persist inside one browser
For a personal note demo with no login or cross-device sync, localStorage can be the smallest valid fix. This is the complete minimal example v1.0, with no external library and no real secrets or personal data.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Refresh persistence check</title>
<style>
body { max-width: 42rem; margin: 3rem auto; padding: 0 1rem;
font-family: system-ui, sans-serif; }
form { display: flex; gap: .5rem; flex-wrap: wrap; }
input { flex: 1; min-width: 12rem; padding: .75rem; }
button { padding: .75rem 1rem; }
#status[data-error="true"] { color: #c62828; }
li { margin: .5rem 0; }
</style>
</head>
<body>
<h1>Notes</h1>
<form id="note-form">
<label for="note">New note</label>
<input id="note" autocomplete="off" required>
<button>Add</button>
</form>
<p id="status" role="status" aria-live="polite"></p>
<ul id="notes"></ul>
<button id="reset" type="button">Clear saved notes</button>
<script>
const STORAGE_KEY = 'vcc-notes-v1';
const form = document.querySelector('#note-form');
const input = document.querySelector('#note');
const list = document.querySelector('#notes');
const status = document.querySelector('#status');
const reset = document.querySelector('#reset');
function setStatus(message, isError = false) {
status.textContent = message;
status.dataset.error = String(isError);
}
let loadFailed = false;
function readNotes() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw === null) return [];
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed) ||
!parsed.every(item => typeof item === 'string')) {
throw new Error('The saved format is not valid.');
}
return parsed;
} catch (error) {
loadFailed = true;
setStatus('Load failed: ' + error.message, true);
return [];
}
}
let notes = readNotes();
function render() {
list.replaceChildren(
...notes.map(text => {
const item = document.createElement('li');
item.textContent = text;
return item;
})
);
}
function writeNotes(nextNotes) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(nextNotes));
setStatus('Saved in this browser.');
return true;
} catch (error) {
setStatus('Save failed: ' + error.message, true);
return false;
}
}
form.addEventListener('submit', event => {
event.preventDefault();
const text = input.value.trim();
if (!text) return;
if (loadFailed) {
setStatus('Not saved because loading failed. Reload to retry, or explicitly clear the exercise data before adding notes.', true);
return;
}
const nextNotes = [...notes, text];
if (!writeNotes(nextNotes)) return;
notes = nextNotes;
render();
form.reset();
input.focus();
});
reset.addEventListener('click', () => {
try {
localStorage.removeItem(STORAGE_KEY);
} catch (error) {
setStatus('Clear failed: ' + error.message, true);
return;
}
loadFailed = false;
notes = [];
render();
setStatus('Saved notes cleared.');
});
render();
</script>
</body>
</html>
Run it: save the file as index.html. In that folder, run python3 -m http.server 8000, then open http://localhost:8000. On Windows with Python Launcher, use py -m http.server 8000.
6. Verify success, failure, and retry
- Success: adding a note shows “Saved in this browser,” and the note survives a refresh on the same origin.
- Failure: if storage is blocked, the page shows “Save failed” and does not pretend the note was persisted. Invalid JSON produces “Load failed.”
- Retry: correct the origin or storage policy, then reload and confirm that existing notes load successfully before adding another note. If the exercise data is corrupt, use the rollback below first.
In Application → Local Storage, inspect the vcc-notes-v1 key and its JSON array. A success message is not enough: verify both the stored value and restoration after refresh.
7. Roll back
Use the Clear saved notes button, or run this line in the Console:
localStorage.removeItem('vcc-notes-v1');
To remove persistence entirely, restore the previous file and remove the readNotes() initialization plus the writeNotes() call. Keep a copy before editing so rollback is one deliberate step.
8. A better prompt for your coding AI
A note appears after I add it, but disappears after refresh.
Do not change frameworks or rewrite the application.
1. Use code evidence to identify whether the value reaches UI memory,
browser storage, or a server.
2. Show where to inspect the Network request and localStorage key.
3. This is a personal demo with no login or cross-device sync.
Add the smallest localStorage-based save and restore path.
4. Surface JSON parsing and blocked-storage failures in the UI.
Do not update the list as if persistence succeeded when the write failed.
5. Provide checks for success, failure, refresh restoration, and reset.
6. End with the changed file/lines and an exact rollback procedure.
This prompt constrains scope, names the acceptable persistence boundary, and defines observable success and failure states. It reduces the chance that the AI adds a database, swaps frameworks, or hides a failed write behind a successful UI update.
9. Common misdiagnoses
- “The button is broken.” If the note appears before refresh, the event and render path probably ran. Persistence is a separate path.
- “The database lost the record.” If the Network panel shows no write request, the database never received one.
- “localStorage syncs across every device.” It is tied to an origin and browser environment, not to a user account.
- “A storage key proves the feature works.” Incorrect read timing, invalid JSON, or a missing initial render can still prevent restoration.
10. When not to use this fix
Do not use localStorage as the default home for passwords, access tokens, sensitive personal data, or payment information. If users need cross-device access, shared editing, permissions, audit history, backups, or conflict handling, a backend and database should be the Source of Truth. Large datasets, structured queries, and transactional writes also exceed this one-line storage pattern.
Connect this Clinic to Sunday’s Fundamentals
This Clinic extends “HTML, CSS, and JavaScript Explained for Vibe Coders — Lesson 1.” Just as changing the rendered page is different from changing the source file, changing UI state is different from persisting data.
Primary sources and verification scope
The primary sources were rechecked on October 7, 2026. The example passed a syntax check and Node-based DOM/localStorage mock tests for writing, refresh restoration, invalid JSON, and blocked writes. The Playwright browser executable was unavailable, so an automated real-browser run was not completed. The WordPress draft was previewed at 1363×936: reading order and code were preserved, the page had no horizontal overflow, and code blocks scrolled independently. A mobile viewport render remains unverified.