diff --git a/backend/routes/messages.ts b/backend/routes/messages.ts index 900aa7b7..3705cb45 100644 --- a/backend/routes/messages.ts +++ b/backend/routes/messages.ts @@ -52,6 +52,57 @@ router.get('/:podId', auth, getMessages); router.post('/:podId', sendMessageRateLimit, auth, createMessage); router.delete('/:id', auth, deleteMessage); +// Backdate a message's `created_at`. Pod-creator-only (or message author); +// no general-purpose user authorization for editing other people's chronology. +// +// Why this exists: chat history demos / fixtures need plausible timestamps +// (yesterday / this morning / etc.) but POST /:podId always stamps with NOW(). +// Direct PG UPDATE works for ops with PG access; this route is the same +// capability for ops without PG access (Aiven IP-allowlisted, kubectl exec +// timing out on new sessions, etc.). +// +// Body: { created_at: ISO8601 string } +router.patch('/:id/created-at', auth, async (req: any, res: any) => { + try { + const { id } = req.params; + const { created_at: createdAtRaw } = req.body || {}; + const ts = createdAtRaw ? new Date(createdAtRaw) : null; + if (!ts || Number.isNaN(ts.getTime())) { + return res.status(400).json({ msg: 'created_at (ISO8601) is required' }); + } + + // Authorization: load the message, then check author or pod-creator. + // eslint-disable-next-line @typescript-eslint/no-require-imports, global-require + const PGMessage = require('../models/pg/Message'); + // eslint-disable-next-line @typescript-eslint/no-require-imports, global-require + const Pod = require('../models/Pod'); + const userId = req.userId || req.user?.id; + if (!userId) return res.status(401).json({ msg: 'auth required' }); + + const message = await PGMessage.findById(id); + if (!message) return res.status(404).json({ msg: 'message not found' }); + + if (String(message.user_id) !== String(userId)) { + const pod = await Pod.findById(message.pod_id); + if (!pod || String(pod.createdBy) !== String(userId)) { + return res.status(403).json({ msg: 'must be message author or pod creator' }); + } + } + + // eslint-disable-next-line @typescript-eslint/no-require-imports, global-require + const { pool } = require('../config/db-pg'); + const r = await pool.query( + 'UPDATE messages SET created_at = $1, updated_at = $1 WHERE id = $2 RETURNING id, created_at', + [ts, id], + ); + if (r.rowCount === 0) return res.status(404).json({ msg: 'message not found in PG' }); + return res.json({ ok: true, id: r.rows[0].id, created_at: r.rows[0].created_at }); + } catch (err: any) { + console.error('backdate route error:', err?.message || err); + return res.status(500).json({ msg: 'backdate failed' }); + } +}); + module.exports = router; export {}; diff --git a/frontend/src/v2/components/V2PodChat.tsx b/frontend/src/v2/components/V2PodChat.tsx index 15216068..a839e078 100644 --- a/frontend/src/v2/components/V2PodChat.tsx +++ b/frontend/src/v2/components/V2PodChat.tsx @@ -572,28 +572,36 @@ const V2PodChat: React.FC = ({ detail, inspectorCollapsed, onTog )} -
- - -
+ {/* Plan / Execute mode toggle — hidden until the pod-mode workflow + ships end-to-end. Currently the toggle persists `mode` to the + pod but no downstream surface uses it for behavior, so the + control reads as broken — clicks land but nothing changes for + the user. Re-enable when the mode actually drives behavior + (agent autonomy gating, suggestion ranking, etc.). */} + {false && ( +
+ + +
+ )} {onOpenInvite && (