๐งญ WebApp Onboarding โ Welcome Modal, Interactive Tour & Theme Wizard๏
ืชืืืื ื-Onboarding ืฉื ื-WebApp ืืืจืื ืืฉืืืฉื ืจืืืืื ืชืืืืื ืฉืืืคืขืืื ืขืืืจ ืืฉืชืืฉืื ืืืฉืื ืืืื: Welcome Modal, ืกืืืจ ืืื ืืจืืงืืืื ืืืืกืก Driver.js ืื-Theme Picker Wizard ืฉืืกืืื ืืช ืืืืืื ืขื ืืชืืื ืืืฉืืช. ืืขืืื ืืจืื ืืช ืืืกืืจืื ืืชืคืขืืืืื, ืื ืื ืื ื ืืืืคืืก ืืื ืงืืืืช ืืืฉืืืืช ืืืคืชืืื.
ืจืืืืื ืืื ืื ืขืืฉืื๏
ืจืืื |
ืืชื ืืืคืืข |
ืืื/ืืคืชื |
ืงืืฆื ืืงืืจ |
|---|---|---|---|
Welcome Modal |
ืื ืืกื ืจืืฉืื ื ืืืจื ืืชืืืจืืช |
|
|
Interactive Tour (Driver.js) |
ืืืจื ืฉื-Welcome Modal ื ืกืืจ ืืื ืชืื |
|
|
Theme Picker Wizard |
ืืกืืื ืืกืืืจ ืื ืืืจื fallback ืฉื 9 ืฉื ืืืช |
|
ืจืืื |
ืชืจืฉืื ืืื ืืจืืงืฆืื๏
ืกืืจ ืืขืื ื:
Welcome Modal ืืืื ืืืกืืจ ืืช ืขืฆืื ืื-DOM (MutationObserver) ืืคื ื ืฉืื ืจืืื ืืืจ ืืชืืื.
Driver.js ืืืชืื ืื ืชืื
/filesืืืกืืื ื-Welcome Modal (waitForWelcomeModal()), ืืื ืืคืขืื ืืช ืืกืืืจ ืืืืชื ืืช ืืืืcodekeeper_walkthrough_v1.Theme Wizard ืืืืื ื-Driver ืืืืฆืขืืช
ThemeWizard.watchDriver()ืืื ื ืคืชื ืืื ืืฉืืกืืืจ ืืกืชืืื. ืื ืืกืืืจ ืื ืจืฅ,scheduleFallback()ืืคืชื ืืืชื ืืืืจ ~9 ืฉื ืืืช.
ืืชืืืช ืืื ืืื ืขืช ืืชื ืืฉืืืืช: Welcome Modal โ Interactive Tour โ Theme Wizard.
Welcome Modal โ ืจืขื ืื ืงืฆืจ๏
ืืืืื Welcome ืืืฆื ืืืฉืชืืฉืื ืฉืืชืืืจืื ื-WebApp ืืคืขื ืืจืืฉืื ื ืืืกืคืง ืงืืฉืืจืื ืืฉืืจืื ืืืืจืืืื ืืืจืืืืื ืืชืื webapp/USER_GUIDE.md. ืืืจื ืื ืคืขืืื (Primary/Secondary/ืืื) ื ืฉืืืช ืืงืฉื ื-POST /api/welcome/ack ืืืืื has_seen_welcome_modal ืืกืืื ื-DB ืืื ื-session.
ืืืจืืช๏
ืืืืจืื ืืฉืชืืฉืื ืืืฉืื ืืฉื ื ืืืจืืืื ืืจืืืืื ืืืจ ืืื ืืืคืฉืจ.
ืืื ืืข ืืืืื ืืื ืืกื ืืจืืฉืื ื โ ืืืคืืข ืคืขื ืืืช ืืืื.
ืืจืืืช ืืฉืชืืฉ๏
show_welcome_modalืืฆื ืืฉืจืช ืืืืืจ true โ ื-template ืืืืฆืจ ืืช ื-modal.welcomeModalInit()ืืฆืืื ืืืื ืืช ืืืคืชืืจืื ืืืจืงืข.ืืงืฉื ืขื ืื ืืคืชืืจ ืงืืจืืช ื-
/api/welcome/ackืืืกืืจื ืืช ืืืืืื.ืืืกื ืคื ืื ืืืืฉื โ MutationObserver ืืกืื ืืืชืจ ืืจืืืืื ืฉืืคืฉืจ ืืืชืืื.
ืืฆืืจืื QA ื ืืชื ืืืืืง ืืช ืืืื ืืฉืืจืืช ื-DB ืื ืื ืงืืช
sessionStorage.has_seen_welcome_modal.
ืกื ืืจืื ืืืืจืื ืืขืืื ื๏
USER_GUIDE.mdืืื ืืงืืจ ืืืืช โ ืืื ืฉืืคืื ื-DB.get_internal_share()ืืืคืฉ ืืช ืืืืืืwelcome/welcome-quickstart(ืืืื ืืืืืื ืืืืกืืืจืืื) ืืืืืืจ ืืช ืชืืื ืืงืืืฅ.ืื ืืงืืืฅ ืืกืจ, ืืืืืื ืืืกืชืจ ืื ืจืฉืืช ืืชืจืื ื-log. ืืืื ืฉืืงืืืฅ ืงืืื ืืื Deploy.
ืืื ืืขืืื๏
ืขืจืื ืืช
webapp/USER_GUIDE.md(ืืืืืื: ืขืืืื โืื ืืืฉโ).ืืืื ืฉืืขืืื
/share/welcome?view=mdืืฆืื ืืช ืืฉืื ืื.ืืื ืฆืืจื ืืืจืืฅ Job ื ืคืจื โ ืืืืืื ืืืฉืื ืืช ืืืงืกื ืืืืืืืืช.
ืงืืฉืืจื ืืืจืืืื (Anchors)๏
Primary:
/share/welcome?view=mdSecondary:
/share/welcome-quickstart?view=md
Best Practices โ ืกืืืื ืืฉืชืืฉืื ืืืฉืื๏
ืกืื ื
has_seen_welcome_modalืื ื-session ืืื ืืื ืืข ืืืืื ืงืืืื ื.ื ืืชื ืืขืฉืืช reuse ืฉื ืืืื ืขืืืจ ืคืืฆืณืจืื โืื ืคืขืืืืโ ื ืืกืคืื.
session: { has_seen_welcome_modal: true }
DB: users.has_seen_welcome_modal = true
ืืืืืช JS โ ืืืืื ืขื Ajax๏
function welcomeModalInit() {
const modal = document.getElementById('welcome-modal');
if (!modal) return;
async function ackAndHide() {
try {
const res = await fetch('/api/welcome/ack', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({})
});
if (!res.ok) throw new Error('ack failed');
try { sessionStorage.setItem('has_seen_welcome_modal', '1'); } catch (e) {}
modal.style.display = 'none';
} catch (e) {
console.warn('welcome ack error', e);
}
}
const primary = modal.querySelector('[data-action="primary"]');
const secondary = modal.querySelector('[data-action="secondary"]');
const skip = modal.querySelector('[data-action="skip"]');
if (primary) primary.addEventListener('click', ackAndHide);
if (secondary) secondary.addEventListener('click', ackAndHide);
if (skip) skip.addEventListener('click', (ev) => {
ev.preventDefault();
ackAndHide();
});
}
window.addEventListener('DOMContentLoaded', () => {
try {
if (sessionStorage.getItem('has_seen_welcome_modal') === '1') return;
} catch (e) {}
welcomeModalInit();
});
API ืงืฉืืจ๏
POST /api/welcome/ackโ ืืกืื ืืช ืืืื ืืืืืืจ{ "ok": true }.POST /api/shared/saveโ ืืฉืืชืืฃ ืืืคืฆื ืฉื ืืืืจืืืื ืขืฆืื.
Interactive Tour (Driver.js)๏
ืืกืืืจ ืืืื ืืจืืงืืืื ืื ืืืืชื ืงืืืฅ (webapp/templates/base.html) ืชืืช initInteractiveWalkthrough(). ืืื ืืืืกืก ืขื Driver.js ืืืืืืฉ ืืช ืืคืขืืืืช ืืืจืืืืืช ืืืกื ืืงืืฆืื.
ืืชื ืืกืืืจ ื ืคืชื?๏
ืืฉืชืืฉ ืืืืืจ (
session.user_id).ืืชืืืช:
/files(ืืืืืจ ื-AUTO_PATHS).localStorage["codekeeper_walkthrough_v1"] !== '1'.ืงืืืืื ืืืื ืืื ืคืขืืืื (ื ืืืง ืขโื
buildSteps()ื-isElementTourVisible()).ื-Welcome Modal ืืืจ ืืืกืจ (MutationObserver).
ืฉืืื ืืกืืืจ๏
ืฉืื |
ืืืื ื (ID) |
ืชืืืืจ |
|---|---|---|
ืคืชืื |
โ |
ืืกืจ โืืจืืืื ืืืืืโ ืืืกืืจ ืงืฆืจ ืขื ืืืจืช ืืกืืืจ. |
ืืฆืืจืช ืงืืืฅ ืืืฉ |
|
ืืฆืื ืืช ืืคืชืืจ ืืืขืืื ืืืืกืคืช ืงืืฆืื/ืงืืขื ืงืื. |
ืืืคืืฉ ืืืืืื |
|
ืืกืืืจ ืขื ืืืืคืืฉ ืืคื ืฉื, ืชืืื, ืชืืืืช ืืฉืคืืช. |
ืืืืกืคืื ืฉืื |
|
ืืืืืฉ ืืช ืชืคืจืื ืืืืกืคืื ืืืชืจืื ืืช ืืฉืืชืืฃ. |
Outro |
โ |
ืืืืขืช ืกืืื ืขื ืจืื ืืืคืขืื ืืืืจืช. |
ืืืืกืคืช ืฉืื ืืืฉ โ ืขืืื ื ืืช buildSteps() ืืืงืคืืื ืฉืืืืื ื ืืงืื ืืืื ืงืืืข ืืืืื.
ืืคืขืื ืืื ืืช ืืืืคืืก๏
URL:
?tour=restartืื?tour=startโ ืืืคืก ืืชcodekeeper_walkthrough_v1, ืืคืขืื ืืช ืืกืืืจ ืืืืืง ืืช ืืคืจืืืจ ืื-URL ืืืืฆืขืืชhistory.replaceState.Console API:
window.CodeKeeperTour.start()โ ืืคืขืื ืืช ืืกืืืจ ืืืงืื.window.CodeKeeperTour.reset()โ ืืืืง ืืช ืืืื ืืืงืืื.window.CodeKeeperTour.hasSeen()โ ืฉืืืืฉื ื-QA.
ื ืืงืื ืืื ื: ืืืืงืช
localStorage.codekeeper_walkthrough_v1ืืฉืืื ืืช ืืืชื ืืคืงื.
ืชืืืืงื ืืืคืชืืื๏
Driver.js ื ืืขื ืืจื
window.driver.js.driver. ืื ืืงืืืฅ ืื ื ืืขื โgetDriverFactory()ืืืืืจnullืืชืจืื ืฉืืืืช ืงืื ืกืื.ืืืืืจืืช (
stageBackground, ืืงืกืื ืืืคืชืืจืื, smoothScroll) ื ืืฆืืืช ืืืืชื ืืืืง โ ืืื ืฆืืจื ืืฉื ืืช ืืช Driver.js ืขืฆืื.ThemeWizard.watchDriver(driver)ื ืงืจื ืืืจืdriver.drive()โ ืื ืชืกืืจื ืืช ืืงืจืืื ืืื ืืฉืืืจ ืขื ืืกืืจ ืืื ืืืืืืืจื.ืืชืืืืช RTL ืืืื ืืช ืืงืืืฅ (
/* ืืชืืืืช RTL ื-Driver.js */). ืื ืืืกืืคืื ืฉืืืื ืืืฉืื, ืืืื ืฉืืืงืกืืื ืงืฆืจืื ืืกืคืืง ืืืกืืื ืฆืจืื.
Troubleshooting โ โืืื ืืกืืืจ ืื ืืืคืืข?โ๏
ืืืงื
localStorage.getItem('codekeeper_walkthrough_v1').ืืืื ืฉืืชื ืื ืชืื
/filesืืืืืืจืื.ืืืืื ืฉืืื ืืืืืืื ืืืจืื ืคืชืืืื โ ื-MutationObserver ืืืชืื ืืืืขืืืืช ืฉืืื.
ืืคืฉื ืืงืื ืกืื ืืืืขื ืืืื
window.driverโ ืืืืื ืืืกืคืจืืื ืื ื ืืขื ื, ืืกืืืจ ืื ืืืคืขื.Content blockers ืฉืืืกืืื localStorage ืขืืืืื ืืื ืืข ืกืืืื ืืื; ืืืงืจื ืืื
?tour=restartืขืฉืื ืฉืื ืืขืืื.
ืฆืืืืื ืืกื / GIF๏
ืืชืจืฉืื ืืืืื ืืช Driver.js ืืขืช ืืืืฉืช ืืคืชืืจ ืืฆืืจืช ืืงืืืฅ, ืืื ืขื ืืคืชืืจื โืืืโ ืโืืืโ ืืขืืจืืช.
Theme Picker Wizard (Personalization)๏
ืืืืืจื ืืคืจืกืื ืืืืฆืื ืืฉืืื ืืช ืชืืืื ื-Onboarding ืืืืคืฉืจ ืืืืืจ Theme ืืืจ ืืืืงืืจ ืืจืืฉืื, ืืืื ืชืฆืืื ืืื ืืงืจืืื ื-API ืืฉืืืจืช ืืืขืืคื.
ืกืงืืจื ืืืืืช๏
ืืืคืขื ืจืง ืืืฉืชืืฉืื ืืืืืจืื ืืื ืชืื
/files.theme_wizard_seenืืื ืข ืืฆืื ืืืืจืช; ืฉืืืจื ืื ืืื ืฉื ืืื ืืกืื ืื ืืช ืืืื.Live Preview ืืชืืฆืข ืืืืฆืขืืช ืขืืืื
document.documentElement.dataset.themeืืืื ืืืช.
ืืจืืืจืื ืืืื ืืช๏
waitForWelcome()ืืืชืื ืฉืืืืืื ืืจืืฉื ืืืฆื ืื-DOM.ืื ืืกืืืจ ืจืฅ โ
ThemeWizard.watchDriver()ืืืืื ืืืืจืืขdestroyedืฉื Driver.js ืืจืง ืื ืคืืชื ืืช ืืืืืืืจื.ืื ืืกืืืจ ืื ืืืคืขื (ืืื ืืืื ืืื/ืืฉืชืืฉ ืขื ืืืืืื) โ
scheduleFallback()ืคืืชื ืืช ืืืืืืืจื ืืืืจ ~9 ืฉื ืืืช.ื ืืชื ืืคืชืื ืืืคืืื ืืจื
?theme_wizard=restartืืwindow.ThemeWizard.open().
ืืืฉืง ืืืืืืืช ืืฉืชืืฉ๏
ืฉืืืฉ ืืคืฉืจืืืืช ืืจืืจืช ืืืื: Nebula, Rose Pine Dawn, Classic (
data-theme-option).ืจืืืืฃ/ืคืืงืืก/ืงืืืง ืืืืืคืื ืืช ื-theme ืืืื ืืืช.
Live Preview ืืืืงื ืืฆื ืืืื (ืชืืืช ื-CSS), ืืืชืืื ืืืื ืชืืื RTL.
ื ืืืฉืืช:
Tabื ืฉืืจ ืืชืื ื-dialog (focus trap).Enter/Spaceืืืืจืื Theme.Escืกืืืจ ืืช ืืืืืืืจื ืืื ืืฉืืืจ.
ืืคืชืืจืื:
ืฉืืืจ ืืืืฉื (
data-action="save") โ ืืคืขืืpersistTheme()+ ืงืจืืื ื-POST /api/ui_prefs.ืืื / ืืืืงืื ืืกืืืจื / ืงืืืง ืขื ืืจืงืข โ ืกืืืจืื ืืื ืฉืืืจื ืื ืืกืื ืื
theme_wizard_seen.
ืืชืืื ืืกื ืืจืื ืฉืจืช๏
localStorage.user_themeโ ืขืจืืช ืื ืืฉื ืฉื ืืืจื.localStorage.dark_mode_preferenceโ ื ืฉืืจืช ืจืง ืขืืืจ ืขืจืืืช ืืืืช (mapDarkModePreference).ืืงืฉื ื-
POST /api/ui_prefsืฉืืืืช{ theme }ืืืืคืฉืจืช ืืฉืจืช ืืืขืื ืืช ื-theme ืืืจ ืืฉืื ื-render ืืื.
๐ ๏ธ Debugging & Reset๏
URL:
?theme_wizard=restart(ืืstart). ืืคืจืืืจ ื ืืืง ืืืืชืืืช ืืืจื ืืคืชืืื.Console:
window.ThemeWizard.open()โ ืคืืชื ืืช ืืืืืืืจื.window.ThemeWizard.resetSeen()โ ืืืืง ืืช ืืืื.window.ThemeWizard.hasSeen()โ ืืืืืจ ืกืืืืก.window.ThemeWizard.scheduleFallback()โ ืืคืขืื ืืืืฉ ืืช ืื ืื ืื ืืืื.
ื ืืงืื ืืื ื: ืืืงื ืืช
theme_wizard_seen,user_theme,dark_mode_preferenceืืื ืืืืืช ืืฉืชืืฉ ืืืฉ.
ืืืจืื ืืืืงืืช (QA)๏
ืคืชืื ืืืื Incognito ืื ื ืงื ืืช ืื ืืคืชืืืช ื-onboarding ื-localStorage.
ืืคืฉืจื ื-Welcome Modal ืืืืคืืข ืืืืืกืืจ, ืืืื ืฉืืกืืืจ ืจืฅ. ืืกืืื, ืฆืคื ืฉืืืืืืืจื ื ืคืชื ืืืืืืืืช.
ืืืงื Live Preview โ ืื ืืืืจื ืฆืจืืื ืืฉื ืืช ืืช
data-themeืืืช ื-CSS.ืืืฆื โืฉืืืจ ืืืืฉืโ ืืืืงื ื-Network ืฉืืชืงืืื ืชืฉืืืช 200 ื-
/api/ui_prefs. ืจืขื ื ื ืืื ืืืืื ืฉืืืืืจื ื ืฉืืจื.ืืืฆื โืืืโ โ ืืืื ืฉืืืืืืืจื ืื ืืืืจ ืืืืจ ืจืขื ืื (ืื
theme_wizard_seenืกืืื).ืืืงื
?theme_wizard=restartโ ืืืืจ ืืคืชืื ืืืคืืื ืืืืืืง ืืช ืืคืจืืืจ ืื-URL.ืืชื ืชืงื (Guest) โ ืืืืื ืฉืืืืืืืจื ืื ืืืฆื ืืืฉืชืืฉืื ืื ืื ืืืืื.
ืฆืืืืื ืืกื๏
ืืืืฉื ืฉื ืืืืืืืจื ืขื ืฉืืืฉ ืขืจืืืช ืื ืืฉื, ืืคืชืืจื ืืคืขืืื ื-Live Preview.
ืงืืฉืืจืื ืจืืืื ืืืื๏
webapp/overview.rst โ ืืกืืจ ืืื ืขื ืืกื ืืืืืจืืช (ืฉืื ืื Theme ืืื ื ืืืืจ ืืื).
webapp/static-checklist.rst โ ืงืืืื ืื ืืื ืื ืืืืืืืช ืื ืืืฉืืช UI.
webapp/api-reference.rst โ ืคืจืืื ื ืืกืคืื ืขื
POST /api/ui_prefs.
Troubleshooting ืืจืืื โ โืืื ืฉืื ืืืจ ืื ืงืืคืฅ ืื?โ๏
ืืืงื localStorage โ
codekeeper_walkthrough_v1,theme_wizard_seen,user_theme,dark_mode_preference. ืืืืงื ืชืืืืจ ืืช ืืืจืฆื.ื ืชืื ืืกืืืืก ืื ืืกื โ ืฉื ื ืืจืืืืื ืจืฆืื ืจืง ื-
/filesืืืืฉืชืืฉ ืืืืืจ.ืืืืืืื ืืืจืื โ ืื ืืืืื ืฉืืืกื ืืช ืืืฃ ืืขืื ืืช ืืกืืืจ/ืืืืืืืจื ืขื ืืืกืจืชื.
Driver.js ืื ืืืื โ ืืื ืืกืคืจืืื ืื ืืืคืขื ืกืืืจ; Theme Wizard ืืืคืขื ืจืง ืืืจื fallback.
ืืืกืื ืชืืื โ ืืจืืืืช ืฉืืื ืขืืช ืืืฉื ื-localStorage ืื ื-MutationObserver ืขืืืืืช ืืฉืืืจ ืืช ืืจืฆืฃ.
ืืืืงืช Console APIs โ
CodeKeeperTour.start()ื-ThemeWizard.open()ืื ืืืจื ืืงืื ืืืืื ืฉืืงืื ืืขืื.
ืืืื ืืืจืืืืช๏
ืชืจืฉืื ืืจืืื:
docs/images/onboarding-flow.svg.ืฆืืืื ืกืืืจ Driver.js:
docs/images/interactive-tour-overview.svg.ืฆืืืื Theme Wizard:
docs/images/theme-wizard-preview.svg.ืืืืจ ืืืกืคืช ืืืื ืืืฉื, ืืจืืฆื
make htmlืืชืืdocs/ืืื ืืืืื ืฉืืื ืืืืจืืช Sphinx.