← back to AbramsOS
claim-fill: positional last-name + Mode->official->form navigation + save discovered admin_url for re-runs
c5337bfb9949c56b3955cec4303ae729d0520870 · 2026-08-18 16:13:46 -0700 · Steve
Files touched
M lib/claim-openclaw-run.jsA public/claim-shots/claim_01kzvyhm9k5hjmkhvyjbdg0xsg.pngM public/css/app.cssM routes/settlements.jsM views/partials/header.ejs
Diff
commit c5337bfb9949c56b3955cec4303ae729d0520870
Author: Steve <steve@designerwallcoverings.com>
Date: Tue Aug 18 16:13:46 2026 -0700
claim-fill: positional last-name + Mode->official->form navigation + save discovered admin_url for re-runs
---
lib/claim-openclaw-run.js | 37 +++++++++++++++
.../claim_01kzvyhm9k5hjmkhvyjbdg0xsg.png | Bin 0 -> 180853 bytes
public/css/app.css | 17 ++++++-
routes/settlements.js | 8 +++-
views/partials/header.ejs | 52 ++++++++++++---------
5 files changed, 89 insertions(+), 25 deletions(-)
diff --git a/lib/claim-openclaw-run.js b/lib/claim-openclaw-run.js
index 2974404..3a8f588 100644
--- a/lib/claim-openclaw-run.js
+++ b/lib/claim-openclaw-run.js
@@ -51,6 +51,7 @@ function inPageFill(values) {
}
var filled = [];
var usedKey = {}; // single-use keys (fill each identity field once)
+ var firstEl = null; // remember the input that got first_name (for positional last-name)
var els = Array.prototype.slice.call(document.querySelectorAll('input, select, textarea'));
for (var i = 0; i < els.length; i++) {
var el = els[i];
@@ -82,9 +83,30 @@ function inPageFill(values) {
el.dispatchEvent(new Event('blur', { bubbles: true }));
}
usedKey[k] = 1; filled.push(k);
+ if (k === 'first_name') firstEl = el;
break;
}
}
+ // POSITIONAL last-name: forms that put "First Name · Middle Initial · Last Name" under one
+ // label leave the last-name input unmatched. Take the empty text inputs after the first-name
+ // input in the same container: [next]=middle (if 3), [last]=last name.
+ if (firstEl && values.last_name && !usedKey.last_name) {
+ try {
+ var cont = firstEl.closest('tr,div,fieldset,section,p') || firstEl.parentElement;
+ var row = Array.prototype.slice.call(cont.querySelectorAll('input')).filter(function (x) { var tt = (x.type || 'text').toLowerCase(); return ['hidden', 'submit', 'button', 'checkbox', 'radio', 'file', 'password'].indexOf(tt) < 0; });
+ var fi = row.indexOf(firstEl);
+ if (fi >= 0 && row.length >= 2) {
+ var lastInput = row[row.length - 1];
+ if (lastInput && lastInput !== firstEl && !lastInput.value) {
+ lastInput.focus(); lastInput.value = values.last_name;
+ lastInput.dispatchEvent(new Event('input', { bubbles: true }));
+ lastInput.dispatchEvent(new Event('change', { bubbles: true }));
+ lastInput.dispatchEvent(new Event('blur', { bubbles: true }));
+ usedKey.last_name = 1; filled.push('last_name');
+ }
+ }
+ } catch (e) {}
+ }
// payment: select PayPal option if the form has one (radio or its label)
var paid = false;
var radios = Array.prototype.slice.call(document.querySelectorAll('input[type=radio]'));
@@ -109,6 +131,21 @@ async function runFill(claimId, formUrl, values) {
page = await browser.newPage(); // OUR tab — not the shared active tab
await page.goto(formUrl, { waitUntil: 'domcontentloaded', timeout: 45000 });
await new Promise((r) => setTimeout(r, 3500)); // let SPA render
+ // If we landed on the Mode aggregator, hop to the OFFICIAL settlement site first
+ // (real Chrome, so Mode's bot-403 doesn't apply). Pick the strongest external claim link.
+ try {
+ if (/modeclassactionsdaily\.com/i.test(page.url())) {
+ const off = await page.evaluate(() => {
+ var here = location.hostname;
+ var links = Array.prototype.slice.call(document.querySelectorAll('a[href^="http"]'));
+ var cand = links.filter(function (e) { try { var u = new URL(e.href); return u.hostname !== here && !/modeclassaction|facebook|twitter|instagram|google|youtube/i.test(u.hostname); } catch (_) { return false; } });
+ var best = cand.find(function (e) { return /file (a )?claim|official (settlement )?(site|website)|claim (here|now|form)|settlement website|visit/i.test(e.textContent || ''); })
+ || cand.find(function (e) { try { return /settlement|claim|litigation|classaction|pnclassaction|dataincident/i.test(new URL(e.href).hostname); } catch (_) { return false; } });
+ return best ? best.href : null;
+ });
+ if (off) { await page.goto(off, { waitUntil: 'domcontentloaded', timeout: 45000 }); await new Promise((r) => setTimeout(r, 3000)); }
+ }
+ } catch (e) {}
// follow an obvious "File a Claim / Submit a Claim / Claim Form" link if we landed on a landing page
try {
const RX = /file a claim|submit a claim|online claim|claim form|start (your )?claim|enter online|online filing|filing site|enter .*filing|proceed to|continue to (the )?claim|begin/i;
diff --git a/public/claim-shots/claim_01kzvyhm9k5hjmkhvyjbdg0xsg.png b/public/claim-shots/claim_01kzvyhm9k5hjmkhvyjbdg0xsg.png
new file mode 100644
index 0000000..deff5c9
Binary files /dev/null and b/public/claim-shots/claim_01kzvyhm9k5hjmkhvyjbdg0xsg.png differ
diff --git a/public/css/app.css b/public/css/app.css
index 4691e63..bc288cb 100644
--- a/public/css/app.css
+++ b/public/css/app.css
@@ -383,10 +383,23 @@ form input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0
display: flex; flex-direction: column; gap: 12px;
overflow-y: auto; z-index: 10;
}
-.sidebar .brand { padding: 4px 8px 6px; }
+.sidebar .brand { display: flex; align-items: center; gap: 8px; padding: 4px 6px 6px; }
+.nav-toggle { background: transparent; border: none; color: var(--text-dim); font-size: 18px; line-height: 1; cursor: pointer; padding: 4px 6px; border-radius: 8px; }
+.nav-toggle:hover { color: var(--text); background: var(--surface-strong); }
.sidenav { display: flex; flex-direction: column; gap: 2px; margin: 0; }
.nav-sec { font-size: 10px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-dim); opacity: .6; padding: 12px 10px 3px; }
-.sidenav a { color: var(--text-dim); font-size: 14px; padding: 8px 10px; border-radius: 8px; }
+.sidenav a { display: flex; align-items: center; gap: 10px; color: var(--text-dim); font-size: 14px; padding: 8px 10px; border-radius: 8px; white-space: nowrap; }
+.sidenav a .ic { flex: 0 0 18px; text-align: center; font-size: 14px; }
+
+/* collapsed icon-rail (toggled by the ☰ button, persisted) */
+html.nav-collapsed .sidebar { flex-basis: 62px; width: 62px; padding: 14px 6px; }
+html.nav-collapsed .sidenav a { justify-content: center; gap: 0; padding: 9px 0; }
+html.nav-collapsed .sidenav a .lb,
+html.nav-collapsed .nav-sec,
+html.nav-collapsed .brand .brand-name,
+html.nav-collapsed .brand .version { display: none; }
+html.nav-collapsed .brand { justify-content: center; }
+html.nav-collapsed .side-actions { flex-direction: column; }
.sidenav a:hover { color: var(--text); background: var(--surface-strong); }
.sidenav a.active { color: var(--text); background: var(--surface-strong); font-weight: 600; }
.side-actions { margin-top: auto; display: flex; align-items: center; gap: 8px; padding: 8px; border-top: 1px solid var(--border); }
diff --git a/routes/settlements.js b/routes/settlements.js
index 1323b8a..a1084e0 100644
--- a/routes/settlements.js
+++ b/routes/settlements.js
@@ -132,7 +132,13 @@ router.post('/api/settlements/:id/run', async (req, res) => {
await db.query(`UPDATE settlement_claim SET eligibility_state='eligible',updated_at=now() WHERE id=$1`, [claim.id]).catch(() => {});
const profile = await loadProfile();
const out = await runFill(claim.id, url, fieldMap(profile));
- if (out.ok) await db.query(`UPDATE settlement_claim SET fill_state='prefilled_awaiting_submit',updated_at=now() WHERE id=$1`, [claim.id]).catch(() => {});
+ if (out.ok) {
+ await db.query(`UPDATE settlement_claim SET fill_state='prefilled_awaiting_submit',updated_at=now() WHERE id=$1`, [claim.id]).catch(() => {});
+ // SAVE the real form URL we walked to, so the next run goes straight there.
+ if (out.on && /^https?:/.test(out.on) && !/modeclassactionsdaily\.com/i.test(out.on) && out.on !== claim.admin_url) {
+ await db.query(`UPDATE settlement_claim SET admin_url=$1,updated_at=now() WHERE id=$2`, [out.on, claim.id]).catch(() => {});
+ }
+ }
res.json(out);
} catch (e) { res.status(503).json({ error: e.message }); }
});
diff --git a/views/partials/header.ejs b/views/partials/header.ejs
index b7ccf6f..dfa6be4 100644
--- a/views/partials/header.ejs
+++ b/views/partials/header.ejs
@@ -10,6 +10,7 @@
try {
var t = localStorage.getItem('theme') || 'dark';
document.documentElement.dataset.theme = t;
+ if (localStorage.getItem('sidenavCollapsed') === '1') document.documentElement.classList.add('nav-collapsed');
} catch (e) { document.documentElement.dataset.theme = 'dark'; }
})();
</script>
@@ -18,34 +19,35 @@
<div class="applayout">
<aside class="sidebar glass">
<div class="brand">
- <a href="/">AbramsOS</a>
+ <button id="navToggle" class="nav-toggle" aria-label="Collapse menu" title="Collapse / expand menu">☰</button>
+ <a href="/" class="brand-name">AbramsOS</a>
<span class="version">v0.1</span>
</div>
<nav class="sidenav">
- <a href="/">Home</a>
+ <a href="/"><span class="ic">🏠</span><span class="lb">Home</span></a>
<div class="nav-sec">Money & Claims</div>
- <a href="/settlements">Settlements</a>
- <a href="/claims">Claims</a>
- <a href="/deadlines">Deadlines</a>
- <a href="/bills">Bills</a>
- <a href="/savings">Savings</a>
- <a href="/assets">Assets</a>
- <a href="/purchases">Purchases</a>
+ <a href="/settlements"><span class="ic">⚖️</span><span class="lb">Settlements</span></a>
+ <a href="/claims"><span class="ic">📋</span><span class="lb">Claims</span></a>
+ <a href="/deadlines"><span class="ic">⏰</span><span class="lb">Deadlines</span></a>
+ <a href="/bills"><span class="ic">🧾</span><span class="lb">Bills</span></a>
+ <a href="/savings"><span class="ic">🐷</span><span class="lb">Savings</span></a>
+ <a href="/assets"><span class="ic">🏦</span><span class="lb">Assets</span></a>
+ <a href="/purchases"><span class="ic">🛍️</span><span class="lb">Purchases</span></a>
<div class="nav-sec">Health</div>
- <a href="/health">Health</a>
- <a href="/biometrics">Biometrics</a>
- <a href="/medications">Meds</a>
- <a href="/prescriptions">Rx</a>
- <a href="/recalls">Recalls</a>
+ <a href="/health"><span class="ic">❤️</span><span class="lb">Health</span></a>
+ <a href="/biometrics"><span class="ic">📈</span><span class="lb">Biometrics</span></a>
+ <a href="/medications"><span class="ic">💊</span><span class="lb">Meds</span></a>
+ <a href="/prescriptions"><span class="ic">📝</span><span class="lb">Rx</span></a>
+ <a href="/recalls"><span class="ic">⚠️</span><span class="lb">Recalls</span></a>
<div class="nav-sec">Home & Life</div>
- <a href="/household">Household</a>
- <a href="/warranties">Warranties</a>
- <a href="/reorders">Reorders</a>
- <a href="/neighborhood">Watch</a>
+ <a href="/household"><span class="ic">🏡</span><span class="lb">Household</span></a>
+ <a href="/warranties"><span class="ic">🛡️</span><span class="lb">Warranties</span></a>
+ <a href="/reorders"><span class="ic">🔁</span><span class="lb">Reorders</span></a>
+ <a href="/neighborhood"><span class="ic">👁️</span><span class="lb">Watch</span></a>
<div class="nav-sec">System</div>
- <a href="/import">Import</a>
- <a href="/connectors">Connectors</a>
- <a href="/audit">Audit</a>
+ <a href="/import"><span class="ic">⬇️</span><span class="lb">Import</span></a>
+ <a href="/connectors"><span class="ic">🔌</span><span class="lb">Connectors</span></a>
+ <a href="/audit"><span class="ic">🔍</span><span class="lb">Audit</span></a>
</nav>
<div class="side-actions">
<% if (typeof userId !== 'undefined' && userId) { %>
@@ -66,7 +68,13 @@
document.querySelectorAll('.sidenav a').forEach(function(a){
var h = a.getAttribute('href');
if (h === p || (h !== '/' && p.indexOf(h) === 0)) a.classList.add('active');
- }); } catch(e){} })();
+ });
+ var tg = document.getElementById('navToggle');
+ if (tg) tg.onclick = function(){
+ var on = document.documentElement.classList.toggle('nav-collapsed');
+ try { localStorage.setItem('sidenavCollapsed', on ? '1' : '0'); } catch(e){}
+ };
+ } catch(e){} })();
</script>
</aside>
<div class="content-wrap">
← b3c0e91 settlements: live openclaw auto-fill runner (dedicated tab,
·
back to AbramsOS
·
claim-fill: combined First/Middle/Last name field -> full na 9676eb8 →