[object Object]

← back to Microsite Engine

ideas dropdown now carries ALL 267 owned domains in three tiers

0c12ece84286f75947f1f5f0898983f25cebd9a1 · 2026-07-17 10:24:54 -0700 · Steve Abrams

- tier 1: niche-matched ideas (domain × catalog tag, product counts, ranked)
- tier 2: every other owned non-expired domain, alphabetical
- tier 3: domains already used by the live fleet, labeled
- picking any domain prefills slug/domain/site name; niche keywords + tagline
  only prefill when there's a real catalog match

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Files touched

Diff

commit 0c12ece84286f75947f1f5f0898983f25cebd9a1
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jul 17 10:24:54 2026 -0700

    ideas dropdown now carries ALL 267 owned domains in three tiers
    
    - tier 1: niche-matched ideas (domain × catalog tag, product counts, ranked)
    - tier 2: every other owned non-expired domain, alphabetical
    - tier 3: domains already used by the live fleet, labeled
    - picking any domain prefills slug/domain/site name; niche keywords + tagline
      only prefill when there's a real catalog match
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
 public/index.html | 21 ++++++++++++++-------
 server.js         | 26 ++++++++++++++++----------
 2 files changed, 30 insertions(+), 17 deletions(-)

diff --git a/public/index.html b/public/index.html
index 3e27d0e..f3a6548 100644
--- a/public/index.html
+++ b/public/index.html
@@ -220,16 +220,23 @@ async function boot(){
     const d=$('domain').value.trim().toLowerCase();
     if(doms.includes(d) && !$('slug').value.trim()) $('slug').value=d.split('.')[0].replace(/[^a-z0-9-]/g,'');
   });
-  // "start from a site idea" — each idea pairs an owned unused domain with the
-  // catalog tags found inside its name (real product counts)
-  $('ideaAdd').innerHTML = '<option value="">💡 pick an idea to prefill the form…</option>' +
-    (FACETS.ideas||[]).map((it,i)=>`<option value="${i}">${esc(it.domain)} — ${esc(it.keywords.join(', '))} · ${it.count.toLocaleString()} products</option>`).join('');
+  // "start from a site idea" — EVERY owned domain, in three tiers: niche-matched
+  // ideas (with real product counts), all other owned domains, then fleet-taken
+  const IDEAS = FACETS.ideas||[];
+  const opt = (it,i)=>`<option value="${i}">${esc(it.domain)}${it.keywords.length?` — ${esc(it.keywords.join(', '))} · ${it.count.toLocaleString()} products`:''}</option>`;
+  const grp = (label,f)=>{ const o=IDEAS.map((it,i)=>f(it)?opt(it,i):'').join(''); return o?`<optgroup label="${label}">${o}</optgroup>`:''; };
+  $('ideaAdd').innerHTML = '<option value="">💡 pick a domain / idea to prefill the form…</option>'
+    + grp('Niche ideas — domain × catalog match', it=>it.keywords.length&&!it.inFleet)
+    + grp('All owned domains', it=>!it.keywords.length&&!it.inFleet)
+    + grp('Already in the fleet', it=>it.inFleet);
   $('ideaAdd').onchange = ()=>{
-    const it=(FACETS.ideas||[])[+$('ideaAdd').value];
+    const it=IDEAS[+$('ideaAdd').value];
     if(!it) return;
     $('slug').value=it.slug; $('domain').value=it.domain; $('siteName').value=it.siteName;
-    $('tagline').value=`Curated ${it.keywords[0].toLowerCase()} wallcoverings for designers and discerning homes.`;
-    $('pos').value=it.keywords.join(', ');
+    if(it.keywords.length){
+      $('tagline').value=`Curated ${it.keywords[0].toLowerCase()} wallcoverings for designers and discerning homes.`;
+      $('pos').value=it.keywords.join(', ');
+    }
     document.querySelectorAll('#tagChips .chip').forEach(c=>c.classList.toggle('on',it.keywords.some(k=>k.toLowerCase()===c.dataset.tag.toLowerCase())));
     syncHeroText(); doPreview();
   };
diff --git a/server.js b/server.js
index ba33bdd..c83011d 100644
--- a/server.js
+++ b/server.js
@@ -110,7 +110,8 @@ function segmentBase(base, vocab) {
 }
 function domainIdeas() {
   const taken = new Set(liveSites().map(s => (s.domain || '').toLowerCase()));
-  const free = ownedDomains().filter(d => !taken.has(d));
+  const owned = ownedDomains();
+  const free = owned.filter(d => !taken.has(d));
   const tagByLower = new Map();
   for (const t of FACETS.tagList) {
     const k = t.name.toLowerCase();
@@ -119,25 +120,30 @@ function domainIdeas() {
   }
   const vocab = [...new Set([...tagByLower.keys(), ...FILLER_WORDS])]
     .filter(w => w.length >= 3).sort((a, b) => b.length - a.length);
-  const ideas = [];
-  for (const dom of free) {
+  // EVERY owned domain is selectable to start a process; ones whose name
+  // contains a real niche tag rank first (with product counts), the rest
+  // follow alphabetically, and fleet-taken domains sit last, labeled.
+  const matched = [], plain = [];
+  for (const dom of owned) {
     const base = dom.split('.')[0].replace(/[^a-z0-9]/g, '');
     const words = segmentBase(base, vocab);
     // filler words (wallpaper/wallcovering/designer/…) help segmentation but are
-    // not a niche — an idea needs at least one REAL niche tag
+    // not a niche — a ranked idea needs at least one REAL niche tag
     const tags = words.filter(w => !FILLER_WORDS.includes(w.replace(/s$/, '')) && !FILLER_WORDS.includes(w))
       .map(w => tagByLower.get(w)).filter(Boolean);
-    if (!tags.length) continue;
-    ideas.push({
+    const idea = {
       domain: dom,
       slug: base,
       siteName: words.map(w => DISPLAY_FIX[w] || (w[0].toUpperCase() + w.slice(1))).join(' '),
       keywords: tags.map(t => t.name),
-      count: Math.max(...tags.map(t => t.n)),
-    });
+      count: tags.length ? Math.max(...tags.map(t => t.n)) : 0,
+      inFleet: taken.has(dom),
+    };
+    if (tags.length && !idea.inFleet) matched.push(idea); else plain.push(idea);
   }
-  ideas.sort((a, b) => b.count - a.count);
-  return { domains: free, ideas: ideas.slice(0, 60) };
+  matched.sort((a, b) => b.count - a.count);
+  plain.sort((a, b) => (a.inFleet - b.inFleet) || a.domain.localeCompare(b.domain));
+  return { domains: free, ideas: [...matched, ...plain] };
 }
 
 // ---- read the LIVE fleet's existing sites (READ-ONLY) for collision guard ----

← d37b802 fix idea site-name segmentation: DP coverage-max splitter +  ·  back to Microsite Engine  ·  ownedDomains: read newer CNCP entries keyed by 'domain' (not 191665d →