- Removed
data.jsfrom<head>— it's now loaded just beforechat.jsat the bottom of<body>, after the DOM is ready. This avoids race conditions. - Removed jQuery — it's unused now. Bootstrap's bundle is sufficient.
- Chat list is now empty markup (
<div id="chatList">) — populated entirely by JS, so adding/removing chats later requires zero HTML changes. <form id="messageForm">replaces the bare<button>— enables native Enter-to-submit and propersubmitevent handling.idattributes added to all interactive elements for clean, unambiguous JS targeting.
Later utilised as index.php:
- Session bootstrap — session_start() and reading $_SESSION for the current user. When you add real auth later, this is the only file you touch.
- Injecting APP_CONFIG into the page — the $jsConfig block emits a <script>const APP_CONFIG = {...}</script> with the API endpoint and current user. JS reads this at startup. No hardcoded values in JS, no extra HTTP round-trip to ask "who am I?".
- HTML escaping — htmlspecialchars() on any server-rendered values (like the username in the nav).
| Module | Responsibility |
|---|---|
ChatStore |
Single source of truth. Holds chat metadata, messages (seeded from data.js), and active chat ID. |
ChatAPI |
All fetch() calls to api/ws.php. GET for history, POST to send. Isolated — swap the endpoint later without touching anything else. |
ChatUI |
Pure DOM rendering. Never reads or mutates state. Takes data in, writes HTML out. |
ChatApp |
Orchestration layer. Wires events, handles ?chat= URL routing via history.pushState, coordinates the other three modules. |
Key behaviours added: SPA routing (no page reload on chat switch), browser back/forward support, typing indicator, error bubble on API failure, send-button disabled state during inflight requests.
Key changes: -ChatAPI._call() is the one place that knows about the action/POST protocol. Every method just calls _call('action_name', payload) and returns json.data directly — the { success, message, data } envelope is unwrapped there, so callers never see it. If the driver changes its envelope format, you fix it in one place.
-
getUserChats → loadChatList— on init, the sidebar is populated from the API, not from data.js. The data.js file can be deleted entirely once the DB is live. -
sendMessageflow — after the user's message is optimistically appended, it POSTs send_message, then immediately calls getMessages again to pick up the bot's reply. It's a deliberate short-term trade-off: one extra fetch instead of building a polling loop now. The comment flags this as the WebSocket/SSE replacement point for Stage 3. -
deleteChat— wired to the new "Delete Chat" dropdown item, calls delete_chat action, hides chatbox, refreshes sidebar, clears URL param. -
clearChat— UI-only, doesn't call the API. Messages stay in the DB; it just clears the local store and re-renders. If you want a "clear for me" feature later, that's a separate delete_messages_for_user endpoint, not this. -
Config object reads from APP_CONFIG with a safe fallback — so the JS still runs if you open the HTML file directly for local testing.
GET ?chat_id=X→ returns{ chat_id, messages: [] }(empty stub; client falls back todata.jsseed)POST { chat_id, message }→ returns a fully-formed bot reply JSON object- Keyword-based dummy reply logic, easy to swap for real AI/workflow
- Response contract is identical to what the real DB layer will return, so
chat.jsneeds no changes in Stage 2
Schema proposed for chat database, designed with Stage 2 in mind:
-- Users (agents, bots, customers)
CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
uuid CHAR(36) NOT NULL UNIQUE, -- public-facing ID
name VARCHAR(120) NOT NULL,
role ENUM('customer','agent','bot') NOT NULL DEFAULT 'customer',
avatar_url VARCHAR(255),
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- Chat sessions
CREATE TABLE chats (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
uuid CHAR(36) NOT NULL UNIQUE, -- maps to ?chat= param
subject VARCHAR(255),
status ENUM('open','closed','pending') NOT NULL DEFAULT 'open',
created_by INT UNSIGNED NOT NULL, -- FK → users.id
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (created_by) REFERENCES users(id)
);
-- Chat participants (many-to-many: chats ↔ users)
CREATE TABLE chat_participants (
chat_id INT UNSIGNED NOT NULL,
user_id INT UNSIGNED NOT NULL,
joined_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (chat_id, user_id),
FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- Messages
CREATE TABLE messages (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
uuid CHAR(36) NOT NULL UNIQUE,
chat_id INT UNSIGNED NOT NULL,
user_id INT UNSIGNED NOT NULL, -- who sent it
type ENUM('sender','reply') NOT NULL, -- matches your JS convention
body TEXT NOT NULL,
read_status TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), -- ms precision for ordering
FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id),
INDEX idx_chat_created (chat_id, created_at) -- the query you'll run most
);
-- Attachments (separate table, not a JSON blob)
CREATE TABLE attachments (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
message_id INT UNSIGNED NOT NULL,
filename VARCHAR(255) NOT NULL,
mime_type VARCHAR(100) NOT NULL,
size_bytes INT UNSIGNED NOT NULL,
storage_path VARCHAR(500) NOT NULL, -- S3 key or local path
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE
);Design rationale:
uuidcolumns everywhere — the?chat=param becomes the chat's UUID, never exposing internal auto-increment IDs to the frontendchat_participantsmakes the model multi-agent-ready (a chat can have a customer + multiple agents + a bot)attachmentsas its own table (not a JSON column) — queryable, reportable, and avoids JSON parsing overheadDATETIME(3)onmessages.created_at— millisecond precision ensures correct ordering when messages arrive in the same second- The
idx_chat_createdindex covers the most common query: "give me all messages for chat X, oldest first"
applicable as of version v1.2
- Responsive layout
- Functional JS/PHP + Database
- CRUD-on message storage and Chat sessions
- Renaming new chats
- soft-deleting chats
- unsend (soft-delete) messages
- Authentication and user/bot discovery
- Search bar not working
- read_status not getting updated
- pagination feature not tested