[object Object]

← back to Rentv

auto-save: 2026-08-05T06:39:44 (5 files) — data/markets.json data/news.json data/ticker.json public/admin/pr-intelligence/pr-shared.js public/admin/pr-intelligence/content-drafts.html

ff6d1c63d2f03bcfaba656907d36c069e717e7c1 · 2026-08-05 06:39:45 -0700 · Steve Abrams

Files touched

Diff

commit ff6d1c63d2f03bcfaba656907d36c069e717e7c1
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Aug 5 06:39:45 2026 -0700

    auto-save: 2026-08-05T06:39:44 (5 files) — data/markets.json data/news.json data/ticker.json public/admin/pr-intelligence/pr-shared.js public/admin/pr-intelligence/content-drafts.html
---
 data/markets.json                                |  24 ++---
 data/news.json                                   |   2 +-
 data/ticker.json                                 |  12 +--
 public/admin/pr-intelligence/content-drafts.html | 114 +++++++++++++++++++++++
 public/admin/pr-intelligence/pr-shared.js        |   1 +
 5 files changed, 134 insertions(+), 19 deletions(-)

diff --git a/data/markets.json b/data/markets.json
index b5fa1c1d..a41faa5b 100644
--- a/data/markets.json
+++ b/data/markets.json
@@ -1,5 +1,5 @@
 {
-  "fetched_at": "2026-08-05T12:53:55.559Z",
+  "fetched_at": "2026-08-05T13:33:57.379Z",
   "source": "Yahoo Finance — live (CBOE Treasury yields, Dow Jones/MSCI REIT indices)",
   "curve": [
     {
@@ -10,15 +10,15 @@
     },
     {
       "label": "5-Year",
-      "value": "4.33%",
-      "change_bps": 0,
-      "dir": "flat"
+      "value": "4.34%",
+      "change_bps": 1,
+      "dir": "up"
     },
     {
       "label": "10-Year",
-      "value": "4.62%",
-      "change_bps": -1,
-      "dir": "down"
+      "value": "4.63%",
+      "change_bps": 0,
+      "dir": "flat"
     },
     {
       "label": "30-Year",
@@ -35,15 +35,15 @@
     },
     {
       "label": "10Y – 3M (curve)",
-      "value": "+89 bps",
+      "value": "+90 bps",
       "dir": "up"
     }
   ],
   "reits": [
     {
       "label": "DJ U.S. Real Estate",
-      "value": "396.09",
-      "change_pct": -0.15,
+      "value": "394.06",
+      "change_pct": -0.51,
       "dir": "down"
     },
     {
@@ -54,8 +54,8 @@
     },
     {
       "label": "Vanguard REIT ETF (VNQ)",
-      "value": "$98.92",
-      "change_pct": -0.03,
+      "value": "$98.30",
+      "change_pct": -0.78,
       "dir": "down"
     }
   ],
diff --git a/data/news.json b/data/news.json
index f65c4ef2..c90bfdff 100644
--- a/data/news.json
+++ b/data/news.json
@@ -1,6 +1,6 @@
 {
  "source": "rentv.com — live",
- "fetched_at": "2026-08-05T12:53:55.312Z",
+ "fetched_at": "2026-08-05T13:33:57.170Z",
  "count": 30,
  "items": [
   {
diff --git a/data/ticker.json b/data/ticker.json
index 9ea98b23..130c4e6b 100644
--- a/data/ticker.json
+++ b/data/ticker.json
@@ -1,5 +1,5 @@
 {
-  "fetched_at": "2026-08-05T12:53:55.530Z",
+  "fetched_at": "2026-08-05T13:33:57.351Z",
   "la_office_vac": {
     "label": "LA OFFICE VAC",
     "value": "23.1%",
@@ -14,16 +14,16 @@
   },
   "cre_index": {
     "label": "CRE INDEX",
-    "value": "4,163",
-    "change_pct": -0.15,
+    "value": "4,142",
+    "change_pct": -0.51,
     "dir": "down",
     "source": "Dow Jones U.S. Real Estate"
   },
   "ten_yr": {
     "label": "10-YR",
-    "value": "4.62%",
-    "change_bps": -1,
-    "dir": "down",
+    "value": "4.63%",
+    "change_bps": 0,
+    "dir": "flat",
     "source": "CBOE 10-Yr (^TNX)"
   }
 }
\ No newline at end of file
diff --git a/public/admin/pr-intelligence/content-drafts.html b/public/admin/pr-intelligence/content-drafts.html
new file mode 100644
index 00000000..356ad55e
--- /dev/null
+++ b/public/admin/pr-intelligence/content-drafts.html
@@ -0,0 +1,114 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
+<title>CRE PR Intelligence — Drafts</title>
+<link rel="stylesheet" href="/admin/pr-intelligence/pr-shared.css">
+<style>
+  .dr-seg{display:inline-flex;border:1px solid var(--line,#d7dde3);border-radius:8px;overflow:hidden;margin:0 0 14px}
+  .dr-seg button{font:inherit;font-size:12.5px;padding:6px 14px;border:0;background:#fff;color:var(--ink,#222);cursor:pointer}
+  .dr-seg button.on{background:#0a66c2;color:#fff}
+  .dr-src{font-size:11px;font-weight:700;border-radius:6px;padding:2px 7px}
+  .dr-src.rentv{background:#e7f0ff;color:#0a66c2} .dr-src.wire{background:#fff2e0;color:#8a5a00}
+  .dr-when{font-size:12px;color:var(--muted,#5b636b);white-space:nowrap}
+  .dr-actions a{font-size:12px;font-weight:600;margin-right:12px;cursor:pointer;text-decoration:none}
+  .dr-actions a.pub{color:#1c7a3e} .dr-actions a.del{color:#b23a1e}
+  .dr-field{margin:0 0 10px} .dr-field label{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted,#5b636b);margin:0 0 4px}
+  .dr-field input,.dr-field select,.dr-field textarea{width:100%;box-sizing:border-box;font:inherit;font-size:13px;padding:8px 10px;border:1px solid var(--line,#d7dde3);border-radius:8px;background:#fff}
+  .dr-field textarea{min-height:220px;resize:vertical}
+</style>
+</head>
+<body>
+<script src="/admin/pr-intelligence/pr-shared.js"></script>
+<script>
+(async function () {
+  const c = PR.frame('content-drafts', 'Drafts',
+    'Blog drafts awaiting review. Outside-wire stories (copyright Option 1) land here as drafts — check the provenance, edit if needed, then Publish (the deliberate human gate) or Delete. Only you turn a wire story live.');
+
+  const seg = document.createElement('div'); seg.className = 'dr-seg';
+  seg.innerHTML = `<button data-s="draft" class="on">📝 Drafts</button><button data-s="published">✅ Published</button>`;
+  c.appendChild(seg);
+  const wrap = document.createElement('div'); c.appendChild(wrap);
+
+  let status = 'draft';
+  const isWire = (s) => s && s !== 'RENTV';
+  const fmtWhen = (iso) => iso ? new Date(iso).toLocaleString(undefined, { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }) : '—';
+
+  const tbl = PR.table([
+    { key: 'title', label: 'Headline', render: (r) => `<b>${PR.esc(r.title || '(untitled)')}</b>${r.dek ? `<br><span style="color:var(--muted,#5b636b);font-size:12px">${PR.esc(String(r.dek).slice(0, 90))}</span>` : ''}` },
+    { key: 'source', label: 'Source', render: (r) => `<span class="dr-src ${isWire(r.source) ? 'wire' : 'rentv'}" title="${isWire(r.source) ? 'Outside wire — publishing is your call' : 'RENTV original'}">${PR.esc(r.source || 'RENTV')}</span>` },
+    { key: 'cat', label: 'Category', render: (r) => PR.badge(r.cat || 'RENTV', 'gray') },
+    { key: 'created_at', label: 'Created', render: (r) => `<span class="dr-when" title="${PR.esc(r.created_at || '')}">🕓 ${fmtWhen(r.created_at)}</span>` },
+    { key: '_a', label: '', render: (r) => `<div class="dr-actions">
+        <a data-act="edit" data-id="${r.id}">Edit</a>
+        <a data-act="preview" data-id="${r.id}" href="/post/${r.id}" target="_blank" rel="noopener noreferrer">Preview</a>
+        ${status === 'draft'
+          ? `<a class="pub" data-act="publish" data-id="${r.id}">🚀 Publish</a>`
+          : `<a data-act="unpublish" data-id="${r.id}">Unpublish</a>`}
+        <a class="del" data-act="delete" data-id="${r.id}">Delete</a>
+      </div>` },
+  ]);
+  wrap.appendChild(tbl.el);
+
+  let rows = [];
+  async function load() {
+    try {
+      const j = await PR.api('/../posts?status=' + status);
+      rows = (j.items || []);
+      tbl.render(rows);
+    } catch (e) { PR.toast(e.message, true); }
+  }
+
+  seg.querySelectorAll('button').forEach((b) => b.addEventListener('click', () => {
+    seg.querySelectorAll('button').forEach((x) => x.classList.remove('on'));
+    b.classList.add('on'); status = b.dataset.s; load();
+  }));
+
+  // Delegated row actions
+  wrap.addEventListener('click', (e) => {
+    const a = e.target.closest('[data-act]'); if (!a) return;
+    const act = a.getAttribute('data-act'), id = a.getAttribute('data-id');
+    const row = rows.find((r) => r.id === id);
+    if (act === 'preview') return; // native link
+    e.preventDefault();
+    if (act === 'edit') return openEditor(row);
+    if (act === 'publish') {
+      if (!confirm(isWire(row.source) ? `Publish this OUTSIDE-WIRE story (${row.source}) live to /blog? This is your editorial call.` : 'Publish this entry live to /blog?')) return;
+      return PR.guard(async () => { await PR.api('/../posts/' + id, { method: 'PUT', body: { status: 'published' } }); load(); }, 'Published');
+    }
+    if (act === 'unpublish') return PR.guard(async () => { await PR.api('/../posts/' + id, { method: 'PUT', body: { status: 'draft' } }); load(); }, 'Moved to draft');
+    if (act === 'delete') {
+      if (!confirm('Delete this draft permanently?')) return;
+      return PR.guard(async () => { await PR.api('/../posts/' + id, { method: 'DELETE' }); load(); }, 'Deleted');
+    }
+  });
+
+  function openEditor(r) {
+    const d = PR.drawer();
+    const body = d.open(`Edit draft ${isWire(r.source) ? '· <span class="dr-src wire">' + PR.esc(r.source) + '</span>' : ''}`, `
+      <div class="dr-field"><label>Headline</label><input id="e_title" value="${PR.esc(r.title || '')}"></div>
+      <div class="dr-field"><label>Category</label>
+        <select id="e_cat">${['Sales','Leases','Financing','Development','Multifamily','Office','Retail','Industrial','People','Markets','RENTV'].map((x) => `<option ${x === (r.cat || 'RENTV') ? 'selected' : ''}>${x}</option>`).join('')}</select></div>
+      <div class="dr-field"><label>Standfirst (dek)</label><input id="e_dek" value="${PR.esc(r.dek || '')}"></div>
+      <div class="dr-field"><label>Image URL</label><input id="e_image" value="${PR.esc(r.image || '')}"></div>
+      <div class="dr-field"><label>Body</label><textarea id="e_body">${PR.esc(r.body || '')}</textarea></div>
+      <div class="dr-field"><button class="btn" id="e_save">💾 Save</button>
+        ${status === 'draft' ? '<button class="btn" id="e_savepub">💾 Save &amp; Publish</button>' : ''}
+        <span class="dr-when" id="e_meta"></span></div>`);
+    const val = (id) => body.querySelector('#' + id).value;
+    const payload = () => ({ title: val('e_title').trim(), cat: val('e_cat'), dek: val('e_dek').trim(), image: val('e_image').trim(), body: val('e_body') });
+    body.querySelector('#e_save').addEventListener('click', () => PR.guard(async () => {
+      await PR.api('/../posts/' + r.id, { method: 'PUT', body: payload() }); d.close(); load();
+    }, 'Saved'));
+    const sp = body.querySelector('#e_savepub');
+    if (sp) sp.addEventListener('click', () => {
+      if (!confirm(isWire(r.source) ? `Publish this outside-wire story (${r.source}) live?` : 'Publish live?')) return;
+      PR.guard(async () => { await PR.api('/../posts/' + r.id, { method: 'PUT', body: { ...payload(), status: 'published' } }); d.close(); load(); }, 'Published');
+    });
+  }
+
+  load();
+})();
+</script>
+</body>
+</html>
diff --git a/public/admin/pr-intelligence/pr-shared.js b/public/admin/pr-intelligence/pr-shared.js
index ad3aa70c..dfdd0074 100644
--- a/public/admin/pr-intelligence/pr-shared.js
+++ b/public/admin/pr-intelligence/pr-shared.js
@@ -11,6 +11,7 @@
     ['review', '✅', 'Review Queue'],
     ['__sepC', '', 'Content'],
     ['content-studio', '📰', 'Content Studio'],
+    ['content-drafts', '📝', 'Drafts'],
     ['linkedin-posting', '📣', 'LinkedIn Posting'],
     ['__sep1', '', 'Outreach'],
     ['campaigns', '📮', 'Campaigns'],

← 6462c58f auto-save: 2026-08-05T06:09:34 (3 files) — data/markets.json  ·  back to Rentv  ·  auto-save: 2026-08-05T07:09:54 (3 files) — data/markets.json f7026ca8 →