Mohammed AL Sarraj commited on
Commit
e85a23d
Β·
1 Parent(s): 8806ae7

feat: add See Example sample outputs to all tools

Browse files
app/tools/changelog_ai/templates/changelog_ai/index.html CHANGED
@@ -156,6 +156,7 @@ refactor: clean up legacy charting utility"></textarea>
156
  <span class="material-symbols-outlined text-5xl text-outline mb-4">auto_awesome</span>
157
  <p class="text-sm font-medium">Paste your commits and click Generate</p>
158
  <p class="text-xs text-outline mt-1">Set version, audience, and language, then generate</p>
 
159
  </div>
160
 
161
  <div id="loading-state" class="hidden flex-1 flex flex-col items-center justify-center gap-4">
@@ -715,6 +716,63 @@ refactor: clean up legacy charting utility"></textarea>
715
  window.loadDemo = loadDemo;
716
 
717
  document.getElementById('btn-try-demo').addEventListener('click', loadDemo);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
718
  })();
719
  </script>
720
  {% endblock %}
 
156
  <span class="material-symbols-outlined text-5xl text-outline mb-4">auto_awesome</span>
157
  <p class="text-sm font-medium">Paste your commits and click Generate</p>
158
  <p class="text-xs text-outline mt-1">Set version, audience, and language, then generate</p>
159
+ <button onclick="window._showChangelogExample()" class="mt-4" style="background:none;border:1px solid #00d97e;color:#00d97e;font-size:12px;padding:3px 10px;border-radius:6px;cursor:pointer;">See example &#x2197;</button>
160
  </div>
161
 
162
  <div id="loading-state" class="hidden flex-1 flex flex-col items-center justify-center gap-4">
 
716
  window.loadDemo = loadDemo;
717
 
718
  document.getElementById('btn-try-demo').addEventListener('click', loadDemo);
719
+
720
+ // ── Example feature ──────────────────────────────────────────────────────
721
+ window._showChangelogExample = function() {
722
+ const exampleData = {
723
+ sections: [
724
+ {
725
+ type: 'features',
726
+ items: [
727
+ { title: 'AI-powered auto-complete for search', description: 'Search bar now suggests results in real-time using a fine-tuned language model, reducing average search time by 60%.' },
728
+ { title: 'Bulk export to CSV and Excel', description: 'Users can now export filtered datasets in both CSV and XLSX formats directly from any table view.' },
729
+ { title: 'Dark mode toggle in user settings', description: 'A system-aware dark mode is now available under Settings > Appearance, with automatic switching based on OS preference.' }
730
+ ]
731
+ },
732
+ {
733
+ type: 'fixes',
734
+ items: [
735
+ { title: 'Dashboard charts not loading on Safari', description: 'Resolved a WebKit rendering issue that caused chart canvases to remain blank on Safari 17+.' },
736
+ { title: 'Email notifications sent twice on password reset', description: 'Fixed a duplicate event trigger in the notification queue that caused two emails per reset request.' }
737
+ ]
738
+ },
739
+ {
740
+ type: 'improvements',
741
+ items: [
742
+ { title: 'Page load time reduced by 40%', description: 'Implemented route-level code splitting and lazy loading for all dashboard modules, bringing initial load under 1.2 seconds.' }
743
+ ]
744
+ }
745
+ ]
746
+ };
747
+
748
+ // Set version and date for the example
749
+ versionInput.value = 'v2.4.0';
750
+ dateInput.value = '2026-04-10';
751
+ lastResult = exampleData;
752
+
753
+ renderChangelog(exampleData);
754
+ showState('results');
755
+ btnDownloadPdf.classList.remove('hidden');
756
+
757
+ // Add clear example button
758
+ if (!document.getElementById('clear-example-btn')) {
759
+ const clearBtn = document.createElement('button');
760
+ clearBtn.id = 'clear-example-btn';
761
+ clearBtn.textContent = 'Clear example';
762
+ clearBtn.style.cssText = 'background:none;border:1px solid #94a3b8;color:#94a3b8;font-size:11px;padding:2px 8px;border-radius:5px;cursor:pointer;margin-top:8px;align-self:center;';
763
+ clearBtn.onclick = function() {
764
+ lastResult = null;
765
+ versionInput.value = '';
766
+ dateInput.valueAsDate = new Date();
767
+ showState('empty');
768
+ btnDownloadPdf.classList.add('hidden');
769
+ this.remove();
770
+ };
771
+ resultsState.appendChild(clearBtn);
772
+ }
773
+ };
774
+
775
+ document.getElementById('btn-try-demo').addEventListener('click', loadDemo);
776
  })();
777
  </script>
778
  {% endblock %}
app/tools/doc_forge/templates/doc_forge/index.html CHANGED
@@ -55,6 +55,10 @@
55
  <div class="flex items-center gap-2"><span class="material-symbols-outlined text-sm">check_circle</span> Instant Results</div>
56
  <div class="flex items-center gap-2"><span class="material-symbols-outlined text-sm">check_circle</span> Free &amp; Open Source</div>
57
  </div>
 
 
 
 
58
 
59
  {# ── Repo Preview Card (hidden until analyze) ──────────────────────────────── #}
60
  <div id="repo-card" class="hidden w-full max-w-2xl mt-8 bg-surface-container-lowest rounded-xl p-6 text-left shadow-sm">
 
55
  <div class="flex items-center gap-2"><span class="material-symbols-outlined text-sm">check_circle</span> Instant Results</div>
56
  <div class="flex items-center gap-2"><span class="material-symbols-outlined text-sm">check_circle</span> Free &amp; Open Source</div>
57
  </div>
58
+ <a href="https://github.com/expressjs/express" target="_blank"
59
+ class="mt-4 inline-flex items-center gap-1" style="color:#00d97e;font-size:12px;text-decoration:none;border:1px solid #00d97e;padding:3px 10px;border-radius:6px;">
60
+ See example: expressjs/express &#x2197;
61
+ </a>
62
 
63
  {# ── Repo Preview Card (hidden until analyze) ──────────────────────────────── #}
64
  <div id="repo-card" class="hidden w-full max-w-2xl mt-8 bg-surface-container-lowest rounded-xl p-6 text-left shadow-sm">
app/tools/git_narrator/templates/git_narrator/index.html CHANGED
@@ -120,6 +120,7 @@
120
  <p class="text-xs text-gd-text-2 max-w-xs leading-relaxed">
121
  Paste a GitHub URL or git log on the left. The AI writes your commit history as an editorial engineering report.
122
  </p>
 
123
  <div class="mt-5 flex flex-wrap justify-center gap-1.5">
124
  {% for tag in ['Highlights', 'Tech Debt', 'Milestones', 'Contributors', 'Impact'] %}
125
  <span class="pill" style="background:#1C2128; color:#484F58; border:1px solid rgba(240,246,252,0.06); font-size:9px;">{{ tag }}</span>
@@ -505,5 +506,94 @@ function buildCopyText(data) {
505
  document.addEventListener('keydown', e => {
506
  if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') doNarrate();
507
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
508
  </script>
509
  {% endblock %}
 
120
  <p class="text-xs text-gd-text-2 max-w-xs leading-relaxed">
121
  Paste a GitHub URL or git log on the left. The AI writes your commit history as an editorial engineering report.
122
  </p>
123
+ <button onclick="showNarratorExample()" class="mt-4" style="background:none;border:1px solid #00d97e;color:#00d97e;font-size:12px;padding:3px 10px;border-radius:6px;cursor:pointer;">See example &#x2197;</button>
124
  <div class="mt-5 flex flex-wrap justify-center gap-1.5">
125
  {% for tag in ['Highlights', 'Tech Debt', 'Milestones', 'Contributors', 'Impact'] %}
126
  <span class="pill" style="background:#1C2128; color:#484F58; border:1px solid rgba(240,246,252,0.06); font-size:9px;">{{ tag }}</span>
 
506
  document.addEventListener('keydown', e => {
507
  if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') doNarrate();
508
  });
509
+
510
+ function showNarratorExample() {
511
+ const exampleData = {
512
+ period_label: 'Mar 18 -- Apr 12, 2026',
513
+ summary_stats: {
514
+ total_commits: 47,
515
+ contributors: 4,
516
+ features: 8,
517
+ fixes: 12
518
+ },
519
+ commits: [
520
+ { hash: 'a3f1b2c', message: 'feat: migrate payment gateway to Stripe V3 API', time: '2 days ago', author: 'sarah' },
521
+ { hash: 'd4e5f6a', message: 'fix: resolve race condition in concurrent checkout', time: '3 days ago', author: 'david' },
522
+ { hash: '7b8c9d0', message: 'refactor: extract payment validation into middleware', time: '4 days ago', author: 'sarah' },
523
+ { hash: 'e1f2a3b', message: 'feat: add webhook handlers for payment events', time: '5 days ago', author: 'carlos' },
524
+ { hash: 'c5d6e7f', message: 'fix: handle expired card tokens gracefully', time: '6 days ago', author: 'david' },
525
+ { hash: 'f8a9b0c', message: 'test: add integration tests for refund flow', time: '1 week ago', author: 'mei' },
526
+ { hash: '2d3e4f5', message: 'feat: implement partial refund support', time: '1 week ago', author: 'carlos' },
527
+ { hash: '6a7b8c9', message: 'fix: correct currency conversion rounding errors', time: '2 weeks ago', author: 'mei' }
528
+ ],
529
+ highlights: [
530
+ {
531
+ title: 'Payment Gateway Migration to Stripe V3',
532
+ narrative: 'The team executed a complete migration from the legacy payment processor to Stripe V3 API. This was the highest-risk change of the quarter, touching 23 files across the checkout, billing, and webhook subsystems. Sarah led the architecture while David handled the concurrency edge cases that surfaced during load testing.',
533
+ impact: 'Payment processing latency reduced by 340ms (avg), failure rate dropped from 2.1% to 0.3%',
534
+ key_commit: 'a3f1b2c feat: migrate payment gateway to Stripe V3 API\nd4e5f6a fix: resolve race condition in concurrent checkout\n7b8c9d0 refactor: extract payment validation into middleware'
535
+ },
536
+ {
537
+ title: 'Partial Refund Support',
538
+ narrative: 'A long-requested feature by the support team -- customers can now receive partial refunds without cancelling the entire order. Carlos implemented the core logic while Mei built out the integration test suite covering 15 edge cases including multi-currency scenarios.',
539
+ impact: 'Support ticket volume for refund issues projected to decrease by 40%'
540
+ }
541
+ ],
542
+ tech_debt: [
543
+ {
544
+ icon: 'cleaning_services',
545
+ title: 'Legacy Payment Module Removed',
546
+ description: 'The old PaymentProcessorV1 class and its 1,200 lines of adapter code were safely removed after the Stripe V3 migration was verified in production for 2 weeks.'
547
+ },
548
+ {
549
+ icon: 'build',
550
+ title: 'Validation Middleware Extracted',
551
+ description: 'Payment validation logic that was duplicated across 4 route handlers has been consolidated into a single middleware, reducing code duplication by 60%.'
552
+ }
553
+ ],
554
+ milestones: [
555
+ {
556
+ status: 'shipped',
557
+ title: 'Stripe V3 Payment Integration',
558
+ narrative: 'Full migration from legacy processor to Stripe V3, including webhook event handling, idempotent retries, and automatic currency conversion. Deployed to production on April 5.',
559
+ contributors: ['sarah', 'david']
560
+ },
561
+ {
562
+ status: 'shipped',
563
+ title: 'Partial Refund System',
564
+ narrative: 'End-to-end partial refund capability with audit trail, multi-currency support, and automated accounting entries.',
565
+ contributors: ['carlos', 'mei']
566
+ },
567
+ {
568
+ status: 'in-progress',
569
+ title: 'PCI DSS Compliance Audit Prep',
570
+ narrative: 'Preparing documentation and code artifacts for the upcoming annual PCI compliance review. Tokenization patterns have been validated, logging has been scrubbed of sensitive data.',
571
+ contributors: ['sarah', 'mei']
572
+ }
573
+ ]
574
+ };
575
+
576
+ renderNarrative(exampleData);
577
+ setStatus('Example loaded');
578
+
579
+ // Add clear example button
580
+ if (!document.getElementById('clear-example-btn')) {
581
+ const clearBtn = document.createElement('button');
582
+ clearBtn.id = 'clear-example-btn';
583
+ clearBtn.textContent = 'Clear example';
584
+ clearBtn.style.cssText = 'background:none;border:1px solid #484F58;color:#484F58;font-size:11px;padding:2px 8px;border-radius:5px;cursor:pointer;margin-top:12px;align-self:center;display:flex;';
585
+ clearBtn.onclick = function() {
586
+ document.getElementById('narrative-output').classList.add('hidden');
587
+ document.getElementById('stats-area').classList.add('hidden');
588
+ document.getElementById('commits-area').classList.add('hidden');
589
+ document.getElementById('copy-btn').classList.add('hidden');
590
+ document.getElementById('period-chip').classList.add('hidden');
591
+ document.getElementById('empty-state').classList.remove('hidden');
592
+ setStatus('Ready');
593
+ this.remove();
594
+ };
595
+ document.getElementById('narrative-output').appendChild(clearBtn);
596
+ }
597
+ }
598
  </script>
599
  {% endblock %}
app/tools/schema_detective/templates/schema_detective/index.html CHANGED
@@ -105,6 +105,7 @@
105
  <span class="material-symbols-outlined text-5xl text-outline mb-4">policy</span>
106
  <p class="text-sm font-medium">Paste your schema and click Run Audit</p>
107
  <p class="text-xs text-outline mt-1">Detects missing PKs, bad indexes, naming issues, and more</p>
 
108
  </div>
109
 
110
  <!-- Loading state -->
@@ -490,6 +491,57 @@ CREATE TABLE prescriptions (
490
  }
491
  }
492
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  btnFix.addEventListener('click', async () => {
494
  const schema = schemaInput.value.trim();
495
  if (!schema) return;
 
105
  <span class="material-symbols-outlined text-5xl text-outline mb-4">policy</span>
106
  <p class="text-sm font-medium">Paste your schema and click Run Audit</p>
107
  <p class="text-xs text-outline mt-1">Detects missing PKs, bad indexes, naming issues, and more</p>
108
+ <button onclick="window._showExample()" class="mt-4" style="background:none;border:1px solid #00d97e;color:#00d97e;font-size:12px;padding:3px 10px;border-radius:6px;cursor:pointer;">See example &#x2197;</button>
109
  </div>
110
 
111
  <!-- Loading state -->
 
491
  }
492
  }
493
 
494
+ // ── Example feature ──────────────────────────────────────────────────────
495
+ window._showExample = function() {
496
+ const exampleData = {
497
+ health_score: 72,
498
+ summary: 'Your e-commerce database has a solid foundation but contains 3 findings that should be addressed. The orders table is missing an index on user_id which will cause slow lookups as data grows. A nullable foreign key in the reviews table risks orphaned records. Several columns use VARCHAR(255) when shorter limits would improve storage and validation.',
499
+ findings: [
500
+ {
501
+ severity: 'critical',
502
+ title: 'Missing index on orders.user_id',
503
+ description: 'The orders table references users via user_id but has no index on this column. Queries joining users to their orders will perform full table scans, degrading rapidly beyond 100K rows.',
504
+ suggestion: 'CREATE INDEX idx_orders_user_id ON orders(user_id);',
505
+ location: 'Table: orders, Column: user_id'
506
+ },
507
+ {
508
+ severity: 'warning',
509
+ title: 'Nullable foreign key in reviews table',
510
+ description: 'The reviews.product_id column allows NULL values despite being a foreign key to products. This can result in orphaned review records that reference no product.',
511
+ suggestion: 'ALTER TABLE reviews MODIFY product_id INT NOT NULL;',
512
+ location: 'Table: reviews, Column: product_id'
513
+ },
514
+ {
515
+ severity: 'advice',
516
+ title: 'Overuse of VARCHAR(255) in multiple tables',
517
+ description: 'Columns users.username, products.sku, and orders.status all use VARCHAR(255). Real-world data in these columns rarely exceeds 50 characters. Tighter limits improve storage efficiency and catch data entry errors.',
518
+ suggestion: 'ALTER TABLE users MODIFY username VARCHAR(64);\nALTER TABLE products MODIFY sku VARCHAR(32);\nALTER TABLE orders MODIFY status VARCHAR(30);',
519
+ location: 'Tables: users, products, orders'
520
+ }
521
+ ]
522
+ };
523
+ _lastFindings = exampleData.findings;
524
+ renderResults(exampleData);
525
+ showState('results');
526
+ sbSummary.classList.remove('hidden');
527
+
528
+ // Add clear example button
529
+ if (!document.getElementById('clear-example-btn')) {
530
+ const clearBtn = document.createElement('button');
531
+ clearBtn.id = 'clear-example-btn';
532
+ clearBtn.textContent = 'Clear example';
533
+ clearBtn.style.cssText = 'background:none;border:1px solid #94a3b8;color:#94a3b8;font-size:11px;padding:2px 8px;border-radius:5px;cursor:pointer;margin:8px auto;display:block;';
534
+ clearBtn.onclick = function() {
535
+ showState('empty');
536
+ sbSummary.classList.add('hidden');
537
+ btnDownloadPdf.classList.add('hidden');
538
+ btnDownloadPdf.classList.remove('flex');
539
+ this.remove();
540
+ };
541
+ resultsState.appendChild(clearBtn);
542
+ }
543
+ };
544
+
545
  btnFix.addEventListener('click', async () => {
546
  const schema = schemaInput.value.trim();
547
  if (!schema) return;
app/tools/sql_whisperer/templates/sql_whisperer/index.html CHANGED
@@ -104,6 +104,7 @@ CREATE TABLE orders (
104
  </div>
105
  <p class="font-semibold text-sys-label text-sm mb-1.5" style="letter-spacing:-0.01em;">Output will appear here</p>
106
  <p class="text-sys-label-3 text-xs max-w-[240px] leading-relaxed">Describe what you need in plain English, paste your schema, then hit Generate.</p>
 
107
  <div class="mt-6 flex flex-wrap justify-center gap-1.5">
108
  {% for tag in ['SELECT', 'JOIN', 'GROUP BY', 'CTE', 'Window Fn', 'Subquery'] %}
109
  <span class="pill" style="background:rgba(116,116,128,0.08); color:#8E8E93; font-family:'SF Mono',monospace; font-size:10px;">{{ tag }}</span>
@@ -366,5 +367,47 @@ function clearAll() {
366
  document.addEventListener('keydown', e => {
367
  if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') generateSQL();
368
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  </script>
370
  {% endblock %}
 
104
  </div>
105
  <p class="font-semibold text-sys-label text-sm mb-1.5" style="letter-spacing:-0.01em;">Output will appear here</p>
106
  <p class="text-sys-label-3 text-xs max-w-[240px] leading-relaxed">Describe what you need in plain English, paste your schema, then hit Generate.</p>
107
+ <button onclick="showSqlExample()" class="mt-4" style="background:none;border:1px solid #00d97e;color:#00d97e;font-size:12px;padding:3px 10px;border-radius:6px;cursor:pointer;">See example &#x2197;</button>
108
  <div class="mt-6 flex flex-wrap justify-center gap-1.5">
109
  {% for tag in ['SELECT', 'JOIN', 'GROUP BY', 'CTE', 'Window Fn', 'Subquery'] %}
110
  <span class="pill" style="background:rgba(116,116,128,0.08); color:#8E8E93; font-family:'SF Mono',monospace; font-size:10px;">{{ tag }}</span>
 
367
  document.addEventListener('keydown', e => {
368
  if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') generateSQL();
369
  });
370
+
371
+ function showSqlExample() {
372
+ const exampleData = {
373
+ sql: `SELECT\n c.customer_name,\n c.region,\n SUM(o.total_amount) AS revenue,\n COUNT(o.id) AS order_count\nFROM customers c\nJOIN orders o\n ON o.customer_id = c.id\nWHERE o.order_date >= DATE_TRUNC('quarter', CURRENT_DATE) - INTERVAL '3 months'\n AND o.order_date < DATE_TRUNC('quarter', CURRENT_DATE)\n AND o.status = 'completed'\nGROUP BY c.customer_name, c.region\nORDER BY revenue DESC\nLIMIT 10;`,
374
+ explanation: 'This query joins customers with their completed orders from last quarter, aggregates total revenue and order count per customer, then returns the top 10 sorted by revenue descending. DATE_TRUNC ensures quarter boundaries are calculated dynamically.',
375
+ warnings: [
376
+ 'Ensure an index exists on orders(customer_id, order_date) for optimal join and filter performance.',
377
+ 'If the orders table exceeds 10M rows, consider partitioning by order_date to reduce scan time.'
378
+ ],
379
+ sample_result_shape: 'customer_name (VARCHAR), region (VARCHAR), revenue (DECIMAL), order_count (INTEGER)',
380
+ alternatives: [
381
+ {
382
+ label: 'CTE approach with window function',
383
+ trade_off: 'More readable for complex pipelines but slightly more overhead on small datasets.',
384
+ sql: `WITH quarterly_orders AS (\n SELECT\n customer_id,\n SUM(total_amount) AS revenue,\n COUNT(*) AS order_count,\n RANK() OVER (ORDER BY SUM(total_amount) DESC) AS rnk\n FROM orders\n WHERE order_date >= DATE_TRUNC('quarter', CURRENT_DATE) - INTERVAL '3 months'\n AND order_date < DATE_TRUNC('quarter', CURRENT_DATE)\n AND status = 'completed'\n GROUP BY customer_id\n)\nSELECT c.customer_name, c.region, q.revenue, q.order_count\nFROM quarterly_orders q\nJOIN customers c ON c.id = q.customer_id\nWHERE q.rnk <= 10\nORDER BY q.revenue DESC;`
385
+ },
386
+ {
387
+ label: 'Subquery with HAVING filter',
388
+ trade_off: 'Avoids window functions for databases that do not support them, but less flexible for tie-breaking.',
389
+ sql: `SELECT c.customer_name, c.region, sub.revenue\nFROM (\n SELECT customer_id, SUM(total_amount) AS revenue\n FROM orders\n WHERE status = 'completed'\n AND order_date >= DATE_TRUNC('quarter', CURRENT_DATE) - INTERVAL '3 months'\n GROUP BY customer_id\n ORDER BY revenue DESC\n LIMIT 10\n) sub\nJOIN customers c ON c.id = sub.customer_id\nORDER BY sub.revenue DESC;`
390
+ }
391
+ ]
392
+ };
393
+
394
+ renderResults(exampleData, 'PostgreSQL');
395
+ setStatus('Example loaded');
396
+
397
+ // Add clear example button
398
+ if (!document.getElementById('clear-example-btn')) {
399
+ const clearBtn = document.createElement('button');
400
+ clearBtn.id = 'clear-example-btn';
401
+ clearBtn.textContent = 'Clear example';
402
+ clearBtn.style.cssText = 'background:none;border:1px solid #AEAEB2;color:#AEAEB2;font-size:11px;padding:2px 8px;border-radius:5px;cursor:pointer;margin-top:8px;align-self:center;';
403
+ clearBtn.onclick = function() {
404
+ document.getElementById('results-container').classList.add('hidden');
405
+ document.getElementById('empty-state').classList.remove('hidden');
406
+ setStatus('Ready');
407
+ this.remove();
408
+ };
409
+ document.getElementById('results-container').appendChild(clearBtn);
410
+ }
411
+ }
412
  </script>
413
  {% endblock %}
app/tools/test_forge/templates/test_forge/index.html CHANGED
@@ -153,6 +153,7 @@
153
  <span class="material-symbols-outlined text-4xl mb-3 text-outline">science</span>
154
  <p class="text-sm font-medium">Paste your code and click Generate Tests</p>
155
  <p class="text-xs text-outline mt-1">Ctrl+Enter to generate Β· 8 frameworks supported</p>
 
156
  </div>
157
 
158
  <!-- Loading state -->
@@ -459,6 +460,131 @@
459
  statPanel.classList.add('hidden');
460
  }
461
  window.loadDemo = loadDemo;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
  })();
463
  </script>
464
  {% endblock %}
 
153
  <span class="material-symbols-outlined text-4xl mb-3 text-outline">science</span>
154
  <p class="text-sm font-medium">Paste your code and click Generate Tests</p>
155
  <p class="text-xs text-outline mt-1">Ctrl+Enter to generate Β· 8 frameworks supported</p>
156
+ <button onclick="window._showTestForgeExample()" class="mt-4" style="background:none;border:1px solid #00d97e;color:#00d97e;font-size:12px;padding:3px 10px;border-radius:6px;cursor:pointer;">See example &#x2197;</button>
157
  </div>
158
 
159
  <!-- Loading state -->
 
460
  statPanel.classList.add('hidden');
461
  }
462
  window.loadDemo = loadDemo;
463
+
464
+ // ── Example feature ──────────────────────────────────────────────────────
465
+ window._showTestForgeExample = function() {
466
+ const exampleCode = `import pytest
467
+ from unittest.mock import patch, MagicMock
468
+ from auth import AuthService, InvalidCredentialsError, AccountLockedError
469
+
470
+
471
+ class TestAuthServiceLogin:
472
+ """Tests for the user authentication login flow."""
473
+
474
+ def test_login_valid_credentials(self):
475
+ svc = AuthService()
476
+ token = svc.login("alice@example.com", "Str0ng!Pass")
477
+ assert token is not None
478
+ assert len(token) == 64
479
+
480
+ def test_login_invalid_email_format(self):
481
+ svc = AuthService()
482
+ with pytest.raises(ValueError, match="Invalid email"):
483
+ svc.login("not-an-email", "password123")
484
+
485
+ def test_login_wrong_password(self):
486
+ svc = AuthService()
487
+ with pytest.raises(InvalidCredentialsError):
488
+ svc.login("alice@example.com", "wrongpass")
489
+
490
+ def test_login_account_locked_after_5_failures(self):
491
+ svc = AuthService()
492
+ for _ in range(5):
493
+ with pytest.raises(InvalidCredentialsError):
494
+ svc.login("alice@example.com", "wrongpass")
495
+ with pytest.raises(AccountLockedError):
496
+ svc.login("alice@example.com", "Str0ng!Pass")
497
+
498
+ def test_login_empty_password_rejected(self):
499
+ svc = AuthService()
500
+ with pytest.raises(ValueError, match="Password required"):
501
+ svc.login("alice@example.com", "")
502
+
503
+ def test_login_returns_unique_tokens(self):
504
+ svc = AuthService()
505
+ t1 = svc.login("alice@example.com", "Str0ng!Pass")
506
+ t2 = svc.login("alice@example.com", "Str0ng!Pass")
507
+ assert t1 != t2
508
+
509
+
510
+ class TestAuthServiceLogout:
511
+ """Tests for session invalidation."""
512
+
513
+ def test_logout_invalidates_token(self):
514
+ svc = AuthService()
515
+ token = svc.login("alice@example.com", "Str0ng!Pass")
516
+ svc.logout(token)
517
+ assert svc.validate_token(token) is False
518
+
519
+ def test_logout_nonexistent_token_is_noop(self):
520
+ svc = AuthService()
521
+ svc.logout("nonexistent-token") # should not raise
522
+
523
+
524
+ class TestAuthServiceTokenValidation:
525
+ """Tests for token validation."""
526
+
527
+ def test_valid_token_returns_user(self):
528
+ svc = AuthService()
529
+ token = svc.login("alice@example.com", "Str0ng!Pass")
530
+ assert svc.validate_token(token) is True
531
+
532
+ def test_expired_token_returns_false(self):
533
+ svc = AuthService()
534
+ token = svc.login("alice@example.com", "Str0ng!Pass")
535
+ svc._expire_token(token)
536
+ assert svc.validate_token(token) is False
537
+
538
+ def test_malformed_token_returns_false(self):
539
+ svc = AuthService()
540
+ assert svc.validate_token("") is False
541
+ assert svc.validate_token(None) is False
542
+
543
+
544
+ class TestPasswordReset:
545
+ """Tests for password reset flow."""
546
+
547
+ @patch("auth.EmailService.send")
548
+ def test_reset_sends_email(self, mock_send):
549
+ svc = AuthService()
550
+ svc.request_password_reset("alice@example.com")
551
+ mock_send.assert_called_once()`;
552
+
553
+ generatedCode = exampleCode;
554
+ testOutput.textContent = exampleCode;
555
+
556
+ bentoTests.textContent = '12';
557
+ bentoCoverage.textContent = '85%';
558
+ bentoCoverageBar.style.width = '85%';
559
+ bentoEdge.textContent = '4';
560
+
561
+ statTestCount.textContent = '12';
562
+ statCoverage.textContent = '85%';
563
+ statCoverageBar.style.width = '85%';
564
+ statEdgeCases.textContent = '4';
565
+ statPanel.classList.remove('hidden');
566
+
567
+ notesText.textContent = 'Tests cover login, logout, token validation, and password reset. Edge cases include account lockout after 5 failures, empty passwords, expired tokens, and malformed inputs.';
568
+ notesRow.classList.remove('hidden');
569
+
570
+ showState('results');
571
+
572
+ // Add clear example button
573
+ if (!document.getElementById('clear-example-btn')) {
574
+ const clearBtn = document.createElement('button');
575
+ clearBtn.id = 'clear-example-btn';
576
+ clearBtn.textContent = 'Clear example';
577
+ clearBtn.style.cssText = 'background:none;border:1px solid #94a3b8;color:#94a3b8;font-size:11px;padding:2px 8px;border-radius:5px;cursor:pointer;position:absolute;top:8px;right:8px;z-index:10;';
578
+ clearBtn.onclick = function() {
579
+ generatedCode = '';
580
+ statPanel.classList.add('hidden');
581
+ showState('empty');
582
+ this.remove();
583
+ };
584
+ resultsState.style.position = 'relative';
585
+ resultsState.appendChild(clearBtn);
586
+ }
587
+ };
588
  })();
589
  </script>
590
  {% endblock %}
docforge.db CHANGED
Binary files a/docforge.db and b/docforge.db differ
 
docforge.db-shm DELETED
Binary file (32.8 kB)
 
docforge.db-wal DELETED
Binary file (33 kB)