diff --git a/app/static/css/portfolio.css b/app/static/css/portfolio.css
index cdf0417..83b13d0 100644
--- a/app/static/css/portfolio.css
+++ b/app/static/css/portfolio.css
@@ -86,6 +86,13 @@
.pf-actions button:disabled { opacity: 0.5; cursor: not-allowed; }
.pf-secondary { color: var(--muted); }
.pf-secondary:hover { color: var(--negative); border-color: var(--negative); }
+/* "Forget this pie" is destructive — only show it while the user is
+ in edit mode (the same mode that surfaces the per-row delete × and
+ the add-position form). Outside of edit mode the row stays in the
+ DOM so existing handlers and any future surface that wants to
+ toggle it can do so without re-rendering. */
+#pf-forget { display: none; }
+#portfolio-panel.pf-editing #pf-forget { display: inline-block; }
.pf-analysis {
margin-top: 14px;
@@ -107,6 +114,24 @@
list-style: none; /* hide native marker in Firefox */
}
.pf-analysis__head::-webkit-details-marker { display: none; }
+.pf-analysis__head-right {
+ display: inline-flex;
+ align-items: center;
+ gap: 12px;
+}
+.pf-regen {
+ background: transparent;
+ border: 1px solid var(--border);
+ color: var(--muted);
+ padding: 3px 9px;
+ font: inherit;
+ font-size: 10.5px;
+ letter-spacing: inherit;
+ cursor: pointer;
+ text-transform: inherit;
+}
+.pf-regen:hover { color: var(--accent); border-color: var(--accent); }
+.pf-regen:disabled { opacity: 0.5; cursor: not-allowed; }
.pf-analysis__head-left::before {
content: "▸ ";
display: inline-block;
diff --git a/app/static/js/portfolio.js b/app/static/js/portfolio.js
index 0f3ecb4..767f078 100644
--- a/app/static/js/portfolio.js
+++ b/app/static/js/portfolio.js
@@ -372,13 +372,24 @@
'' +
'
' + rows + '' +
'' +
+ // The "Generate" button only renders when there's no cached
+ // analysis yet. Once one exists, regeneration moves inside the
+ // collapsible analysis box (see showAnalysis below). The "Forget
+ // this pie" button is destructive enough that it lives in
+ // edit-mode only — CSS in portfolio.css hides it when the
+ // portfolio panel isn't carrying the .pf-editing class.
'
' +
'';
- document.getElementById('pf-analyze').addEventListener('click', () => runAnalysis(pie, enriched));
+ const analyzeBtn = document.getElementById('pf-analyze');
+ if (analyzeBtn) {
+ analyzeBtn.addEventListener('click', () => runAnalysis(pie, enriched));
+ }
document.getElementById('pf-forget').addEventListener('click', () => {
if (confirm('Remove the saved pie from this browser? The server holds nothing — this is local.')) {
clearPie();
@@ -390,13 +401,16 @@
// wipe it. Rendered expanded so the user keeps seeing the body they
// just generated — collapsing it under their cursor every minute
// reads as "the analysis disappeared". They can still click the
- // header to collapse manually within a single refresh window.
+ // header to collapse manually within a single refresh window. The
+ // regenerate callback closes over the current pie/enriched so a
+ // click rebuilds the analysis with the same context that drove
+ // the initial render.
if (pie.analysis && pie.analysis.content) {
- showAnalysis(pie.analysis, { open: true });
+ showAnalysis(pie.analysis, { open: true }, () => runAnalysis(pie, enriched));
}
}
- function showAnalysis(analysis, opts) {
+ function showAnalysis(analysis, opts, onRegenerate) {
const out = document.getElementById('pf-analysis');
if (!out) return;
const openAttr = (opts && opts.open) ? ' open' : '';
@@ -407,20 +421,43 @@
'' +
'AI analysis' +
'' +
- '' +
- esc((analysis.generated_at || '').slice(0, 19).replace('T', ' ')) +
- ' UTC' +
+ '' +
+ '' +
+ esc((analysis.generated_at || '').slice(0, 19).replace('T', ' ')) +
+ ' UTC' +
+ (onRegenerate
+ ? ''
+ : '') +
+ '' +
'' +
'
' + esc(analysis.content) + '
' +
'';
+ if (onRegenerate) {
+ const regen = document.getElementById('pf-regen');
+ if (regen) {
+ regen.addEventListener('click', (e) => {
+ // The button lives inside ; clicking it would
+ // normally toggle the open/closed. Suppress the
+ // default toggle and the bubble so only our regen runs.
+ e.preventDefault();
+ e.stopPropagation();
+ onRegenerate();
+ });
+ }
+ }
}
async function runAnalysis(pie, enriched) {
const out = document.getElementById('pf-analysis');
- const btn = document.getElementById('pf-analyze');
+ // First-run click is on pf-analyze; the regenerate path is pf-regen
+ // inside the details summary. Either may be the live trigger.
+ const btn = document.getElementById('pf-analyze') ||
+ document.getElementById('pf-regen');
out.hidden = false;
out.innerHTML = '
generating…
';
- btn.disabled = true;
+ if (btn) btn.disabled = true;
// Build the prices payload from the universe cache so the server
// doesn't have to re-fetch.
@@ -458,11 +495,17 @@
}
// Persist before rendering so auto-refresh can re-hydrate.
saveAnalysis(data);
- showAnalysis(data, { open: true });
+ // Pass the regenerate callback so the in-details "Regenerate"
+ // button shows up on the freshly-rendered analysis too.
+ showAnalysis(data, { open: true }, () => runAnalysis(pie, enriched));
} catch (e) {
out.innerHTML = '
' + esc(e.message) + '
';
} finally {
- btn.disabled = false;
+ // The original button may have been replaced by showAnalysis →
+ // re-fetch its handle (or null if neither id is on the page now).
+ const liveBtn = document.getElementById('pf-analyze') ||
+ document.getElementById('pf-regen');
+ if (liveBtn) liveBtn.disabled = false;
}
}