Adding skills & isac talent updates
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
---
|
||||
name: create-plan
|
||||
description: Create a concise plan. Use when a user explicitly asks for a plan related to a coding task.
|
||||
metadata:
|
||||
short-description: Create a plan
|
||||
---
|
||||
|
||||
# Create Plan
|
||||
|
||||
## Goal
|
||||
|
||||
Turn a user prompt into a **single, actionable plan** delivered in the final assistant message.
|
||||
|
||||
## Minimal workflow
|
||||
|
||||
Throughout the entire workflow, operate in read-only mode. Do not write or update files.
|
||||
|
||||
1. **Scan context quickly**
|
||||
- Read `README.md` and any obvious docs (`docs/`, `CONTRIBUTING.md`, `ARCHITECTURE.md`).
|
||||
- Skim relevant files (the ones most likely touched).
|
||||
- Identify constraints (language, frameworks, CI/test commands, deployment shape).
|
||||
|
||||
2. **Ask follow-ups only if blocking**
|
||||
- Ask **at most 1–2 questions**.
|
||||
- Only ask if you cannot responsibly plan without the answer; prefer multiple-choice.
|
||||
- If unsure but not blocked, make a reasonable assumption and proceed.
|
||||
|
||||
3. **Create a plan using the template below**
|
||||
- Start with **1 short paragraph** describing the intent and approach.
|
||||
- Clearly call out what is **in scope** and what is **not in scope** in short.
|
||||
- Then provide a **small checklist** of action items (default 6–10 items).
|
||||
- Each checklist item should be a concrete action and, when helpful, mention files/commands.
|
||||
- **Make items atomic and ordered**: discovery → changes → tests → rollout.
|
||||
- **Verb-first**: “Add…”, “Refactor…”, “Verify…”, “Ship…”.
|
||||
- Include at least one item for **tests/validation** and one for **edge cases/risk** when applicable.
|
||||
- If there are unknowns, include a tiny **Open questions** section (max 3).
|
||||
|
||||
4. **Do not preface the plan with meta explanations; output only the plan as per template**
|
||||
|
||||
## Plan template (follow exactly)
|
||||
|
||||
```markdown
|
||||
# Plan
|
||||
|
||||
<1–3 sentences: what we’re doing, why, and the high-level approach.>
|
||||
|
||||
## Scope
|
||||
- In:
|
||||
- Out:
|
||||
|
||||
## Action items
|
||||
[ ] <Step 1>
|
||||
[ ] <Step 2>
|
||||
[ ] <Step 3>
|
||||
[ ] <Step 4>
|
||||
[ ] <Step 5>
|
||||
[ ] <Step 6>
|
||||
|
||||
## Open questions
|
||||
- <Question 1>
|
||||
- <Question 2>
|
||||
- <Question 3>
|
||||
```
|
||||
|
||||
## Checklist item guidance
|
||||
Good checklist items:
|
||||
- Point to likely files/modules: src/..., app/..., services/...
|
||||
- Name concrete validation: “Run npm test”, “Add unit tests for X”
|
||||
- Include safe rollout when relevant: feature flag, migration plan, rollback note
|
||||
|
||||
Avoid:
|
||||
- Vague steps (“handle backend”, “do auth”)
|
||||
- Too many micro-steps
|
||||
- Writing code snippets (keep the plan implementation-agnostic)
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Vipul Gupta
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
name: frontend-design
|
||||
description: Create distinctive, production-grade frontend interfaces with high design quality. Use when building or refining web components, pages, or apps (HTML/CSS/JS/React) with bold aesthetics, purposeful layouts, motion, and accessibility. Avoid generic AI-looking output.
|
||||
---
|
||||
|
||||
# Frontend Design
|
||||
|
||||
## Overview
|
||||
|
||||
Build memorable, functional interfaces with a clear aesthetic direction (no generic templates). Focus on bold typography, cohesive palettes, purposeful motion, semantic structure, and accessibility.
|
||||
|
||||
## Quick Start
|
||||
- Collect intent: purpose, audience, platform constraints, framework, delivery format (HTML/React/Vue).
|
||||
- Choose an aesthetic direction and signature move (stripe, glow, grain, magazine stack). See `references/aesthetic-playbook.md`.
|
||||
- Define tokens: fonts, palette, shadows, radii, spacing scale. Load fonts early.
|
||||
- Plan layout: hero + supporting sections, data/timeline rails, cards; pick grid system and motion plan.
|
||||
- Build semantic markup, wire CSS variables, add motion with staggered reveals, then run accessibility and responsive checks.
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1) Define intent and aesthetic
|
||||
- Ask for: target users, tone (brutalist, editorial, neon, soft craft, industrial, etc.), content types (cards, forms, charts), performance constraints.
|
||||
- Pick a single memorable gesture and stick to it. Avoid mixed-font chaos unless the style calls for it.
|
||||
- If the user is vague, propose 2–3 aesthetic directions from `references/aesthetic-playbook.md` and confirm.
|
||||
|
||||
### 2) Plan structure and tokens
|
||||
- Establish CSS variables for palette, shadows, radii, spacing, and motion curve. Keep accent count to 1–2.
|
||||
- Choose font pairing (display + body, optional mono). Provide fallbacks after custom fonts.
|
||||
- Map sections and micro-interactions. For vanilla builds, start from semantic HTML; for React, break into composable pieces (Hero, FeatureList, Timeline, CTA Rail).
|
||||
- Reference `references/implementation-patterns.md` for layout, motion, accessibility, and responsive patterns.
|
||||
|
||||
### 3) Build and animate
|
||||
- Markup: semantic headings, skip links, ARIA labels for interactive elements, descriptive alt text.
|
||||
- Layout: prefer CSS Grid for macro and Flexbox for clusters. Break the grid deliberately (asymmetry, overlaps) while preserving readability.
|
||||
- Styling: use custom backgrounds (gradient meshes, grain, hatch) to avoid flat color; design buttons and cards with intentional borders/shadows.
|
||||
- Motion: orchestrate a few meaningful animations (page-load stagger, hover magnetic lift, scroll reveals). Guard with `prefers-reduced-motion`.
|
||||
- Data-driven UI: define arrays of content (cards, metrics, steps) to keep components DRY and easy to restyle.
|
||||
|
||||
### 4) Polish and QA
|
||||
- Responsive: collapse grids, maintain padding, ensure CTAs remain prominent on mobile, swap hover cues for focus/active styles.
|
||||
- Accessibility: one `h1`, logical heading order, focus styles, contrast AA+, labeled inputs, `aria-live` where status changes.
|
||||
- Performance: avoid heavy shadows on mobile, compress/limit assets, use font-display swap, prefer CSS animations over JS-heavy effects.
|
||||
|
||||
## References
|
||||
- `references/aesthetic-playbook.md`: Pick strong aesthetic directions with fonts, colors, layout, and motion cues.
|
||||
- `references/implementation-patterns.md`: HTML/React patterns, tokens, motion recipes, responsive and accessibility guidance.
|
||||
|
||||
## Assets
|
||||
- `assets/vanilla-starter/`: Ready-to-run vanilla HTML/CSS/JS concept (avant studio theme) with gradients, grain, staggered reveals, skip link, and accessible structure. Copy and retheme by updating CSS variables, fonts, and content.
|
||||
@@ -0,0 +1,131 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Avant Studio</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:wght@500;700&family=Newsreader:opsz,wght@6..72,400;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main">Skip to content</a>
|
||||
<div class="grain"></div>
|
||||
<header class="top-bar">
|
||||
<div class="logo">Avant Studio</div>
|
||||
<nav>
|
||||
<a href="#main">Work</a>
|
||||
<a href="#process">Process</a>
|
||||
<a href="#contact">Contact</a>
|
||||
</nav>
|
||||
<button class="cta">Start a project</button>
|
||||
</header>
|
||||
|
||||
<main id="main">
|
||||
<section class="hero">
|
||||
<div class="hero-copy">
|
||||
<p class="eyebrow">Design lab</p>
|
||||
<h1>Striking, production-ready frontends without the generic glaze.</h1>
|
||||
<p class="lede">We craft visceral digital spaces that move fast, feel tactile, and stay memorable. Built with intent, tuned for launch.</p>
|
||||
<div class="actions">
|
||||
<button class="cta primary">See capabilities</button>
|
||||
<button class="ghost">View a live concept</button>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div><span>42</span> shipped sites</div>
|
||||
<div><span>95%</span> lighthouse perf</div>
|
||||
<div><span>0</span> generic templates</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-card reveal">
|
||||
<div class="tag">Now launching</div>
|
||||
<div class="card-body">
|
||||
<h2>Neon Observatory</h2>
|
||||
<p>A retro-futurist control room with live telemetry, aurora gradients, and glitch hover states.</p>
|
||||
<div class="pill">CSS Grid</div>
|
||||
<div class="pill">Framer Motion</div>
|
||||
<div class="pill">ARIA-first</div>
|
||||
<div class="line"></div>
|
||||
<div class="meta">
|
||||
<span>ETA: 2 weeks</span>
|
||||
<span>Visual QA locked</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="grid" id="process">
|
||||
<article class="panel reveal">
|
||||
<p class="eyebrow">01 / Intent</p>
|
||||
<h3>Pick a bold narrative, define the signature move, lock the palette.</h3>
|
||||
<p>We interrogate purpose, audience, and constraints. Each build commits to one unmistakable gesture—stripe, glow, or grain—and sticks to it.</p>
|
||||
</article>
|
||||
<article class="panel reveal">
|
||||
<p class="eyebrow">02 / Build</p>
|
||||
<h3>Real code: semantic HTML, layered CSS, purposeful motion.</h3>
|
||||
<p>CSS variables for theming, responsive grids, progressive enhancement. Animations gated by prefers-reduced-motion.</p>
|
||||
</article>
|
||||
<article class="panel reveal">
|
||||
<p class="eyebrow">03 / Polish</p>
|
||||
<h3>Accessibility, performance, and tactile interactions.</h3>
|
||||
<p>Contrast checks, keyboard navigation, tuned shadows, intentional hover states. We avoid boilerplate aesthetics at all costs.</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="timeline" id="contact">
|
||||
<div class="timeline-head">
|
||||
<p class="eyebrow">Field notes</p>
|
||||
<h3>Recent drops</h3>
|
||||
</div>
|
||||
<div class="timeline-rail">
|
||||
<div class="dot"></div>
|
||||
<div class="dot"></div>
|
||||
<div class="dot"></div>
|
||||
</div>
|
||||
<div class="timeline-items">
|
||||
<div class="timeline-card reveal">
|
||||
<p class="label">2024</p>
|
||||
<h4>Aerial atlas</h4>
|
||||
<p>Editorial map portal with split-screen narrative and scroll-synced chapters.</p>
|
||||
</div>
|
||||
<div class="timeline-card reveal">
|
||||
<p class="label">2023</p>
|
||||
<h4>Glassfoundry</h4>
|
||||
<p>Industrial dashboard with translucent panels, border glows, and live system vitals.</p>
|
||||
</div>
|
||||
<div class="timeline-card reveal">
|
||||
<p class="label">2022</p>
|
||||
<h4>Islands</h4>
|
||||
<p>Pastel travel companion with hand-drawn dividers, slow float animations, and tactile cards.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="marquee" aria-hidden="true">
|
||||
<span>Distinctive frontends · Real code · No generic glaze · </span>
|
||||
<span>Distinctive frontends · Real code · No generic glaze · </span>
|
||||
<span>Distinctive frontends · Real code · No generic glaze · </span>
|
||||
</div>
|
||||
<div class="footer-grid">
|
||||
<div>
|
||||
<p class="eyebrow">Contact</p>
|
||||
<a href="mailto:hello@avant.studio">hello@avant.studio</a>
|
||||
</div>
|
||||
<div>
|
||||
<p class="eyebrow">Social</p>
|
||||
<a href="#">Are.na</a>
|
||||
<a href="#">Behance</a>
|
||||
</div>
|
||||
<div>
|
||||
<p class="eyebrow">Collab</p>
|
||||
<p>We ship components, pages, systems, and microsites.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="./script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,13 @@
|
||||
const observer = new IntersectionObserver(
|
||||
entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('visible');
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: 0.2 }
|
||||
);
|
||||
|
||||
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
|
||||
@@ -0,0 +1,425 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:wght@500;700&family=Newsreader:opsz,wght@6..72,400;600&display=swap');
|
||||
|
||||
:root {
|
||||
--bg: #0b0c10;
|
||||
--fg: #f6f1e6;
|
||||
--muted: #a7acba;
|
||||
--accent: #ff6b4a;
|
||||
--accent-2: #6b9bff;
|
||||
--card: rgba(255, 255, 255, 0.03);
|
||||
--stroke: rgba(255, 255, 255, 0.12);
|
||||
--radius: 18px;
|
||||
--shadow: 0 18px 44px rgba(0, 0, 0, 0.35);
|
||||
--ease: cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Newsreader', 'Times New Roman', serif;
|
||||
color: var(--fg);
|
||||
background: var(--bg);
|
||||
min-height: 100vh;
|
||||
line-height: 1.7;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background-image:
|
||||
radial-gradient(1200px at 15% 20%, rgba(107, 155, 255, 0.25), transparent 50%),
|
||||
radial-gradient(1400px at 80% 0%, rgba(255, 107, 74, 0.2), transparent 50%),
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.03), transparent);
|
||||
}
|
||||
|
||||
.grain {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background:
|
||||
radial-gradient(circle at 20% 20%, rgba(255, 255, 255, 0.04), transparent 35%),
|
||||
repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.02) 1px, transparent 1px, transparent 3px);
|
||||
mix-blend-mode: soft-light;
|
||||
opacity: 0.7;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
left: -999px;
|
||||
top: 0;
|
||||
padding: 12px 16px;
|
||||
background: var(--accent);
|
||||
color: #0b0c10;
|
||||
border-radius: 8px;
|
||||
font-weight: 700;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.skip-link:focus {
|
||||
left: 16px;
|
||||
top: 16px;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 18px 36px;
|
||||
backdrop-filter: blur(10px);
|
||||
background: rgba(11, 12, 16, 0.75);
|
||||
border-bottom: 1px solid var(--stroke);
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-family: 'Bricolage Grotesque', 'Arial Black', sans-serif;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
gap: 18px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
nav a {
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid transparent;
|
||||
transition: border-color 220ms var(--ease), color 220ms var(--ease);
|
||||
}
|
||||
|
||||
nav a:hover,
|
||||
nav a:focus-visible {
|
||||
border-color: var(--stroke);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: 'Bricolage Grotesque', sans-serif;
|
||||
font-weight: 700;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cta {
|
||||
padding: 12px 18px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(120deg, var(--accent), var(--accent-2));
|
||||
color: #0b0c10;
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25);
|
||||
transition: transform 200ms var(--ease), box-shadow 200ms var(--ease);
|
||||
}
|
||||
|
||||
.cta:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 16px 44px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr 0.8fr;
|
||||
gap: 32px;
|
||||
padding: 96px 36px 48px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hero-copy h1 {
|
||||
font-family: 'Bricolage Grotesque', sans-serif;
|
||||
font-size: clamp(2.4rem, 5vw, 3.6rem);
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.03em;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.hero .lede {
|
||||
max-width: 56ch;
|
||||
color: var(--muted);
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.18em;
|
||||
font-size: 0.78rem;
|
||||
color: var(--accent);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin: 24px 0 18px;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
padding: 12px 16px;
|
||||
border-radius: 999px;
|
||||
background: transparent;
|
||||
border: 1.5px solid var(--stroke);
|
||||
color: var(--fg);
|
||||
transition: border-color 220ms var(--ease), color 220ms var(--ease), transform 220ms var(--ease);
|
||||
}
|
||||
|
||||
.ghost:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
color: var(--muted);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.stats span {
|
||||
display: block;
|
||||
font-family: 'Bricolage Grotesque', sans-serif;
|
||||
font-size: 1.5rem;
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--stroke);
|
||||
border-radius: 22px;
|
||||
padding: 24px;
|
||||
box-shadow: var(--shadow);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-card::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -30%;
|
||||
background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.04), transparent 45%);
|
||||
transform: rotate(8deg);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.hero-card .tag {
|
||||
display: inline-block;
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
color: var(--accent);
|
||||
font-size: 0.85rem;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.hero-card .card-body {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.hero-card h2 {
|
||||
font-family: 'Bricolage Grotesque', sans-serif;
|
||||
margin: 12px 0 8px;
|
||||
}
|
||||
|
||||
.hero-card p {
|
||||
color: var(--muted);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.pill {
|
||||
display: inline-block;
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid var(--stroke);
|
||||
margin: 4px 6px 4px 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.line {
|
||||
margin: 16px 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, var(--accent), transparent);
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: var(--muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 18px;
|
||||
padding: 48px 36px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
border: 1px solid var(--stroke);
|
||||
border-radius: var(--radius);
|
||||
padding: 22px;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
|
||||
.panel h3 {
|
||||
margin: 6px 0 10px;
|
||||
font-family: 'Bricolage Grotesque', sans-serif;
|
||||
}
|
||||
|
||||
.panel p {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.timeline {
|
||||
padding: 24px 36px 80px;
|
||||
border-top: 1px solid var(--stroke);
|
||||
}
|
||||
|
||||
.timeline-head h3 {
|
||||
font-family: 'Bricolage Grotesque', sans-serif;
|
||||
margin: 8px 0 16px;
|
||||
}
|
||||
|
||||
.timeline-rail {
|
||||
position: relative;
|
||||
height: 3px;
|
||||
background: var(--stroke);
|
||||
border-radius: 999px;
|
||||
margin: 24px 0 32px;
|
||||
}
|
||||
|
||||
.timeline-rail .dot {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
background: var(--accent);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 6px rgba(255, 107, 74, 0.15);
|
||||
}
|
||||
|
||||
.timeline-rail .dot:nth-child(1) { left: 8%; }
|
||||
.timeline-rail .dot:nth-child(2) { left: 50%; }
|
||||
.timeline-rail .dot:nth-child(3) { left: 88%; }
|
||||
|
||||
.timeline-items {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.timeline-card {
|
||||
border: 1px solid var(--stroke);
|
||||
border-radius: var(--radius);
|
||||
padding: 18px;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.timeline-card .label {
|
||||
font-size: 0.9rem;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--accent);
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.timeline-card h4 {
|
||||
margin: 4px 0 8px;
|
||||
font-family: 'Bricolage Grotesque', sans-serif;
|
||||
}
|
||||
|
||||
.timeline-card p {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.footer {
|
||||
border-top: 1px solid var(--stroke);
|
||||
padding: 28px 36px 48px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.marquee {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
gap: 16px;
|
||||
font-family: 'Bricolage Grotesque', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.14em;
|
||||
color: var(--muted);
|
||||
animation: scroll 18s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes scroll {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(-50%); }
|
||||
}
|
||||
|
||||
.footer-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.footer-grid a {
|
||||
display: block;
|
||||
color: var(--fg);
|
||||
text-decoration: underline;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(22px);
|
||||
transition: transform 320ms var(--ease), opacity 320ms var(--ease);
|
||||
}
|
||||
|
||||
.reveal.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.hero {
|
||||
grid-template-columns: 1fr;
|
||||
padding-top: 72px;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.actions {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
# Aesthetic Playbook
|
||||
|
||||
Use these ready-to-go directions to avoid generic looks. Pick one and commit; swap fonts/colors as needed but keep the intent.
|
||||
|
||||
## Brutalist Raw
|
||||
- Fonts: Display `Bricolage Grotesque` or `Migra`; Body `IBM Plex Sans` or `Atkinson Hyperlegible`.
|
||||
- Colors: Harsh contrast (charcoal/ivory) with one acidic accent (#ff5c39 or #f2ff49). Keep surfaces flat with thick borders.
|
||||
- Layout: Grid-breaking blocks, oversized headings, visible rules, obvious gutters. Use all-caps labels and numeric tags.
|
||||
- Motion: Snappy entrance (0.35s) with slight overshoot; hover states that invert colors or reveal underlines.
|
||||
- Backgrounds: Checkerboard, dotted patterns, or single bold stripe angled across the hero.
|
||||
|
||||
## Editorial / Magazine
|
||||
- Fonts: Display `Canela`, `Fraunces`, or `Cormorant Garamond`; Body `Newsreader` or `Source Serif 4`; Accents `DM Mono`.
|
||||
- Colors: Muted palettes (deep ink, warm off-white, burnt orange accents). Let photos bleed to the edge.
|
||||
- Layout: Tall columns, asymmetrical whitespace, floating pull-quotes, generous line-height. Use hairline rules and small caps.
|
||||
- Motion: Slow fades (0.6s) and subtle parallax on hero imagery. Stagger text reveals by 80–120ms.
|
||||
- Backgrounds: Fine grain or paper texture overlay with low opacity.
|
||||
|
||||
## Retro-Futuristic Neon
|
||||
- Fonts: Display `Space Mono` or `Bungee`; Body `Sora` or `DM Sans`.
|
||||
- Colors: Dark base (#05060e) with neon accents (#61f4de, #ff6ad5, #8af5ff). High glow shadows.
|
||||
- Layout: Angled cards, pill buttons, floating ribbons, console-style stats. Use thin outlines and scanlines.
|
||||
- Motion: Glitch keyframes, sweep highlights on hover, marquee strips for metadata.
|
||||
- Backgrounds: Gradient mesh with cyan/magenta, layered with transparent grid lines or starfield dots.
|
||||
|
||||
## Soft Craft / Pastel
|
||||
- Fonts: Display `Clash Display` or `Epilogue`; Body `Manrope` or `Sen`.
|
||||
- Colors: Warm pastel base (#f6ede3) with clay/terracotta (#d96b52) and sage (#8ca28f) accents.
|
||||
- Layout: Rounded blobs, gentle shadows (20–30px blur), stacked cards with stitched borders, hand-drawn icons if available.
|
||||
- Motion: Ease-in-out float (2–4s) on background shapes; hover lifts with shadow color shifts.
|
||||
- Backgrounds: Irregular gradient blobs, subtle noise at 8–12% opacity to avoid flatness.
|
||||
|
||||
## Industrial / Utilitarian
|
||||
- Fonts: Display `Compacta` or `Archivo Black`; Body `Barlow` or `Work Sans`; Numerics `JetBrains Mono`.
|
||||
- Colors: Cold neutrals (#101010, #e6e6e6) with hazard accents (#ffb400 or #2de1c2). Use muted teal for secondary.
|
||||
- Layout: Rigid grids, labeled panels, visible coordinates and data tags, boxed callouts with corner ticks.
|
||||
- Motion: Linear 0.25–0.35s transitions; sliding panels; progress bars with stripes.
|
||||
- Backgrounds: Carbon fiber or diagonal hatch via CSS repeating-linear-gradients; add soft inner glow for depth.
|
||||
|
||||
## How to Choose
|
||||
1. Identify the emotional goal (edgy, luxe, calm, industrial, playful).
|
||||
2. Pick fonts first, then colors that suit the fonts’ character.
|
||||
3. Decide on a single signature move (checkerboard stripe, neon glow, magazine pull-quote). Everything else supports it.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Implementation Patterns
|
||||
|
||||
Use these patterns to move quickly while keeping the aesthetic sharp. Load only sections you need.
|
||||
|
||||
## Vanilla HTML/CSS/JS Workflow
|
||||
- Start with semantic structure: `header`, `main`, `section`, `article`, `footer`. Keep nav skip-links (`a.skip-nav` to `#main`).
|
||||
- Define theme tokens up top:
|
||||
```css
|
||||
:root { --bg: #0b0c10; --fg: #f7f2e9; --accent: #ff6b4a; --muted: #9aa1b5; --radius: 18px; --shadow: 0 18px 40px rgba(0,0,0,0.35); }
|
||||
```
|
||||
- Load fonts via `<link>` or `@import`; set base styles on `body` (smooth scrolling, background texture, max width handling).
|
||||
- Layout: use CSS Grid for macro layout, Flexbox for inline clusters. Mix asymmetry: stagger column widths (e.g., `grid-template-columns: 1.1fr 0.9fr`).
|
||||
- Motion: prefer CSS keyframes and `transition: transform 320ms var(--ease)`; orchestrate with delays on child elements. Use `prefers-reduced-motion` guards.
|
||||
- Interactivity: lean on small JS to toggle classes, e.g., IntersectionObserver for reveal animations.
|
||||
|
||||
## React Patterns
|
||||
- Create a `Theme` object for tokens and pass via context or props. Keep components focused (Hero, FeatureList, TestimonialRail).
|
||||
- Use CSS Modules, styled-components, or Tailwind? If Tailwind is mandated, extend the config with custom fonts, gradients, and shadows; avoid defaults-only look.
|
||||
- Animations: prefer Framer Motion for page-load + hover effects; stagger `variants` and keep durations <0.5s.
|
||||
- Data-driven UI: define arrays for cards, metrics, timelines; map to components to keep markup DRY and easily themeable.
|
||||
- Accessibility: forward refs for focusable elements; use `aria-live` for status, `aria-pressed` for toggles, and proper `alt` text for imagery.
|
||||
|
||||
## Layout & Spacing
|
||||
- Hero patterns: split-screen with oversized headline and vertical nav stripe; magazine stack with overlapping cards; timeline rail with labeled markers.
|
||||
- Micro-layout: use 4/8 spacing scale; keep heading-to-body ratio ~1.6–1.8; limit max-width of prose to 68–76ch.
|
||||
- Negative space vs density: if maximalist, constrain chaos inside frames; if minimal, increase margins and align to a visible grid.
|
||||
|
||||
## Color, Texture, and Backgrounds
|
||||
- Always define CSS variables for palette. Keep 1–2 primary + 1 accent. Add noise overlays to avoid sterile flatness:
|
||||
```css
|
||||
.grain { position: fixed; inset: 0; pointer-events: none; background: radial-gradient(circle at 20% 20%, rgba(255,255,255,.04), transparent 40%), repeating-linear-gradient(90deg, rgba(255,255,255,.018), rgba(255,255,255,.018) 1px, transparent 1px, transparent 3px); mix-blend-mode: soft-light; opacity: 0.7; }
|
||||
```
|
||||
- Gradient mesh idea: layer multiple radial gradients with different sizes and opacities; rotate a pseudo-element slowly.
|
||||
- Borders: try 1.5–2px strokes with `border-image` gradients for subtle sheen.
|
||||
|
||||
## Motion Recipes
|
||||
- Staggered reveal: apply `transform: translateY(20px); opacity: 0;` then add `.visible` to animate to neutral.
|
||||
- Magnetic hover: on buttons/cards, use `transition: transform 220ms ease, box-shadow 220ms ease;` and slightly rotate/translate toward cursor.
|
||||
- Hero load: fade-in background first, then text with 80ms steps, then accent lines sliding in from edges.
|
||||
|
||||
## Responsive Guidance
|
||||
- Collapse grids to single column below 900px; center align key text; maintain comfortable padding (min 18–22px).
|
||||
- Replace hover-dependent cues with clear focus/active styles. Increase tap targets to 44px minimum.
|
||||
- Test with viewport meta tag, clamp() for font sizes, and avoid fixed heights; use `minmax` grids.
|
||||
|
||||
## QA Checklist
|
||||
- Color contrast passes WCAG AA for text/background. Verify focus order and skip link.
|
||||
- Assets load with sensible fallbacks (system font stack declared after customs).
|
||||
- Animations respect `prefers-reduced-motion: reduce`.
|
||||
- Verify semantics: one `h1`, landmarks defined, inputs labeled, buttons not masquerading as links.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: import-talent-screenshot
|
||||
description: Extract a Division Resurgence talent from one or more screenshots, isolate its icon, reconcile Tier I and Tier II wording and values, create any missing mechanic prerequisites, and enter the reviewed talent through SHD.build ISAC. Use when the user supplies talent screenshots or asks to import, create, model, or enter a pictured talent.
|
||||
---
|
||||
|
||||
# Import Talent Screenshot
|
||||
|
||||
Create a complete, modeled talent from screenshots. Submit mutations to the authenticated ISAC server action so validation, permissions, relationships, icon handling, and audit logging remain intact. Prefer direct requests over operating the editor UI.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Inspect every supplied screenshot at original detail.
|
||||
2. Extract the displayed title, description, highlighted values, thresholds, durations, stacks, and other mechanically meaningful wording.
|
||||
3. Classify a title ending in `II` as Tier II. Classify the matching title without `II` as Tier I. Store one talent under the base title without the tier suffix. Do not strip a numeral that is genuinely part of the base name; ask when the screenshots do not establish the distinction.
|
||||
4. Reconcile both tiers into one shared description template. Replace tier-varying values with numbered placeholders in reading order. Use `{duration}` and `{max_stacks}` only when the mechanic defines them.
|
||||
5. Ask the user for exactly one required choice before entry: `Weapon Talent 1`, `Weapon Talent 2`, `Backpack`, or `Body Armor`. Do not infer this choice from the icon or talent wording.
|
||||
6. Ask other questions only when missing evidence would materially change stored data or combat behavior. State the unresolved alternatives precisely.
|
||||
7. Search the Talent registry for the same base name and category. Update only when the user explicitly requests an update; otherwise stop on a duplicate.
|
||||
8. Search active attributes, supported mechanic recipes, runtime events, outcomes, scopes, resources, and state behavior before creating prerequisites. Reuse semantically identical records.
|
||||
9. Implement missing mechanic functionality when the screenshot establishes behavior that the runtime cannot yet represent. Extend the shared mechanic types, validation, persistence, ISAC editor, runtime execution, pseudo-script, reference UI, schema migration, and focused tests as required. Preserve generic semantics; do not add talent-name checks or a one-off recipe when a reusable event, outcome, scope, resource, or state rule fits. Follow the repository's database-preservation procedure before applying a schema migration.
|
||||
10. Create a missing attribute or other data requirement through its authenticated ISAC server action only when its name, unit, type, category, and calculation semantics are established by the screenshots or project data. Ask for any material field that cannot be derived safely.
|
||||
11. Model the mechanic with the narrowest supported recipe. Prefer health-threshold bands for wording such as “while below N% Health.” Treat those bands as mutually exclusive strongest-matching ranges unless the source explicitly says effects stack.
|
||||
12. Set each numbered outcome's Tier I and Tier II values, phase, scope, basis, and attribute. Mark the mechanic `READY` only after the full behavior is executable and tested. Use `ASSUMPTIONS` or `CAPTURE ONLY` only when source evidence is incomplete, not merely because runtime functionality is missing.
|
||||
13. Isolate the icon from the screenshot. Preserve the pictured silhouette rather than redesigning it. Remove text and UI background, center it on a square transparent canvas, retain antialiased edges, and validate it at small display size. Use the image-generation editing workflow only when a faithful crop and local cleanup are insufficient.
|
||||
14. Build one `multipart/form-data` payload matching the current talent form contract, including the prepared icon and serialized mechanic program. Submit it directly to the authenticated `/isac/talents?/saveTalent` server action. Use browser-side `fetch` when the browser holds the authenticated session. Do not open, populate, click, or submit the talent editor. Do not use the editor's file input to stage the icon. Make the icon available to the browser-side request without interacting with the editor, then attach it to the `FormData` as a `File`. Inspect the current route source before relying on a field name or action path.
|
||||
15. Read the saved registry row back. Verify the name, category, icon, both rendered tier descriptions, modeled state, thresholds, values, and any newly implemented runtime behavior. Confirm the successful `game_data` audit event when audit access is available.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Never invent values hidden by cropping, blur, overlays, or missing tiers.
|
||||
- Preserve strict wording: “below 25%” means `< 25`, not `<= 25`.
|
||||
- Do not add tier values together. For ordered health thresholds, select the value for the current non-overlapping band unless the source states that effects accumulate.
|
||||
- Do not create a near-duplicate attribute because of capitalization, spacing, or a synonymous label.
|
||||
- Do not expose credentials, session data, or personal data in screenshots or audit checks.
|
||||
- Never write talent, tier, effect, mechanic, icon, or audit rows directly to the database. Never call lower-level persistence helpers to bypass the route action.
|
||||
- Do not manually create an audit event. A successful server action must create it.
|
||||
- Never use the talent editor UI for an import. If direct submission cannot preserve the authenticated session, file upload, or current form contract, stop and report the blocker.
|
||||
- Preview extracted fields and planned mechanics before the first external mutation when the user has not already approved entry.
|
||||
|
||||
## Project Reference
|
||||
|
||||
Read [references/shd-talent-entry.md](references/shd-talent-entry.md) before entering a talent. Re-check the referenced source files when the application schema or editor changes.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Import Talent Screenshot"
|
||||
short_description: "Create modeled talents from game screenshots"
|
||||
default_prompt: "Use $import-talent-screenshot to extract and enter a talent from my screenshots."
|
||||
@@ -0,0 +1,58 @@
|
||||
# SHD Talent Entry Reference
|
||||
|
||||
## Canonical surfaces
|
||||
|
||||
- Talent editor: `src/routes/isac/talents/+page.svelte`
|
||||
- Talent action and validation: `src/routes/isac/talents/+page.server.ts`
|
||||
- Mechanic editor: `src/lib/components/isac/MechanicProgramEditor.svelte`
|
||||
- Mechanic draft types: `src/lib/calculations/mechanic-editor.ts`
|
||||
- Mechanic runtime and validation: `src/lib/calculations/mechanic.ts`
|
||||
- Mechanic persistence: `src/lib/server/conditional-effects.ts`
|
||||
- Supported recipes: `src/lib/calculations/mechanic-recipes.ts`
|
||||
- Attribute registry and actions: `src/routes/isac/attributes/`
|
||||
- Icon validation and persistence: `src/lib/server/game-data-icons.ts`
|
||||
|
||||
Treat these files as the current authority. Inspect them before acting when fields or behavior may have changed.
|
||||
|
||||
When a pictured mechanic is not representable, add the smallest reusable runtime capability and its validation, persistence, editor controls, migration, reference entry, and tests before entering the talent. A missing runtime feature is an implementation task, not a reason by itself to leave a talent under assumptions.
|
||||
|
||||
## Request path
|
||||
|
||||
Prefer a direct authenticated request to the SvelteKit talent action over editor interaction:
|
||||
|
||||
- Load or inspect `/isac/talents` to obtain current registry data and confirm the authenticated session.
|
||||
- Construct `FormData` from the current contract in `+page.svelte` and `+page.server.ts`.
|
||||
- Include the icon as the `icon` file part and the complete mechanic program as serialized `conditionalRule`.
|
||||
- Submit one `POST` request to `/isac/talents?/saveTalent` with an accepted SvelteKit action response type.
|
||||
- Parse the action result, then query the registry and audit surfaces for readback.
|
||||
|
||||
Do not treat the action path as a public, version-stable REST API. Re-check the route implementation when it changes. Do not insert into the embedded database or call persistence utilities directly.
|
||||
|
||||
## Talent form
|
||||
|
||||
The user-facing categories are:
|
||||
|
||||
- `Weapon Talent 1`
|
||||
- `Weapon Talent 2`
|
||||
- `Backpack`
|
||||
- `Body Armor`
|
||||
|
||||
Talent descriptions use one shared template. Number tier-varying outcomes as `{1}`, `{2}`, and so on in reading order. The action derives Tier I and Tier II descriptions from each outcome's variant values.
|
||||
|
||||
## Mechanic entry
|
||||
|
||||
Use the `health_threshold_bands` recipe for non-stacking current-health ranges. Enter thresholds from strongest to weakest in the editor and assign one outcome to each band. Verify exact boundary behavior after saving.
|
||||
|
||||
For the Spirited example:
|
||||
|
||||
- Base name: `Spirited`
|
||||
- Tier I: 9% below 75% Health; 12% below 50%; 15% below 25%
|
||||
- Tier II: 12.60% below 75% Health; 16.80% below 50%; 21% below 25%
|
||||
- Shared template: `Grants +{1} Damage Reduction while below 75% Health. Grants +{2} Damage Reduction while below 50% Health. Grants +{3} Damage Reduction while below 25% Health.`
|
||||
- Attribute: `Damage Reduction`, unit `%`
|
||||
- Scope: self
|
||||
- Phase: while active
|
||||
- Basis: flat percentage points
|
||||
- Bands: `[50,75)`, `[25,50)`, and `[0,25)`; no effect at or above 75%
|
||||
|
||||
Confirm that `Damage Reduction` already exists before creating it. Confirm the category/slot with the user before saving.
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
name: import-weapon-screenshot
|
||||
description: Extract a Division Resurgence weapon from screenshots, match pictured talent icons to existing SHD.build talents, prepare its weapon image, collect missing tier and augmentation values, and enter the reviewed weapon through ISAC. Use when the user supplies weapon-detail, blueprint, crafting, talent-grid, or weapon-stat screenshots and asks to import, create, model, or enter the weapon.
|
||||
---
|
||||
|
||||
# Import Weapon Screenshot
|
||||
|
||||
Create a complete weapon record from screenshots without guessing hidden configuration. Submit mutations to the authenticated ISAC server action so validation, relationships, and audit logging remain intact. Prefer direct requests over operating the editor UI.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Inspect every screenshot at original detail. Extract the weapon name, category, native tier, damage type, fixed handling values, visible damage range, talent icons, and augmentation evidence.
|
||||
2. Search the Weapon registry by normalized name. Stop on a duplicate unless the user explicitly requests an update. Treat named variants such as `First Wave` as distinct weapons.
|
||||
3. Reconcile screenshot labels with project vocabulary:
|
||||
- `Piercing Damage` → `Pierce`
|
||||
- `Blast Damage` → `Blast`
|
||||
- `Shredding Damage` → `Shredding`
|
||||
- Preserve the displayed native tier.
|
||||
4. Ask directly for missing damage-range numbers for every tier at or above the native tier. Do not request extra screenshots when typed minimum and maximum values are faster.
|
||||
5. Read each Talent 1 and Talent 2 grid as a complete, unordered set. These weapon screenshots show every available talent twice: once for Tier I and once for Tier II. Group identical icons across the entire grid, resolve each two-icon group against active talents in the corresponding ISAC slot, and store the matching talent once. Compare silhouettes and internal details only. Tier variants may appear in different rows, separated by other talents, or in any order; never use adjacency, sequence, or grid position to pair them.
|
||||
6. For an ambiguous or unmatched talent icon, identify its slot and screenshot position and request a close-up showing that talent's icon, name, and description. After the user supplies it, invoke `import-talent-screenshot` to create and model the missing talent, then resume the weapon import.
|
||||
7. Never infer talents that are cropped below the screenshot. Ask for another talent-grid screenshot when the visible grid may continue.
|
||||
8. Ask the user to type Tactical Augmentation 01 as one or two `Attribute = value` pairs. Reuse exact active attributes and stop on ambiguous names.
|
||||
9. Treat weapon mod slots as unknown unless the user supplies explicit evidence. Save the weapon with no mod-slot configuration; expect ISAC to display `CONFIG REQUIRED`. Do not borrow slots from a related weapon.
|
||||
10. Extract the weapon render from the screenshot. Preserve the pictured model, attachments, materials, and proportions. Remove UI, cursor, text, and background; center the weapon on a transparent canvas with useful padding. Use the image-generation editing workflow only when a faithful crop and local cleanup are insufficient.
|
||||
11. Search for every prerequisite before creating it. If the current schema or runtime cannot represent established weapon behavior, implement the smallest reusable capability, including validation, persistence, editor support, migrations, reference material, and tests. Follow database-preservation rules before applying schema changes.
|
||||
12. Preview the extracted fields, resolved talents, unresolved evidence, and planned ISAC payload before the first mutation unless the user has already approved entry.
|
||||
13. Build one `multipart/form-data` payload matching the current weapon form contract and submit it directly to the authenticated `/isac/weapons?/save` server action. Use browser-side `fetch` when the authenticated session is held by the browser; do not click through the editor merely to populate fields. Inspect the current route source before relying on a stale field name or action path.
|
||||
14. Read the saved registry row back. Verify identity, category, native tier, damage type, fixed attributes, all required tier ranges, talent pools, augmentation effects, image, and expected configuration state. Confirm the `game_data` audit event when audit access is available.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Do not infer tier damage ranges by scaling another tier or related weapon.
|
||||
- Do not match a talent from resemblance alone when more than one active icon is plausible.
|
||||
- Every talent in these weapon talent grids appears twice, once per tier. Deduplicate identical icons across the complete slot grid into one weapon talent option, regardless of their order or position.
|
||||
- Do not assume the screenshot shows the complete talent list when the panel continues beyond the frame.
|
||||
- Do not infer weapon mod slots.
|
||||
- Do not create a near-duplicate talent or attribute because of spelling, capitalization, or synonymous labels.
|
||||
- Preserve numeric precision exactly as displayed or typed.
|
||||
- Never write weapon, tier, talent-option, augmentation, mod-slot, image, or audit rows directly to the database. Never call lower-level persistence helpers to bypass the route action.
|
||||
- Do not manually create an audit event. A successful server action must create it.
|
||||
- Use the UI only when direct submission cannot preserve an authenticated session, file upload, or current form contract.
|
||||
- Keep authored database data intact and preserve unrelated worktree changes.
|
||||
|
||||
## Project Reference
|
||||
|
||||
Read [references/shd-weapon-entry.md](references/shd-weapon-entry.md) before entering a weapon. Re-check the referenced source files when the editor or schema changes.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Import Weapon Screenshot"
|
||||
short_description: "Import pictured weapons into SHD.build"
|
||||
default_prompt: "Use $import-weapon-screenshot to extract and enter this pictured weapon."
|
||||
@@ -0,0 +1,48 @@
|
||||
# SHD Weapon Entry Reference
|
||||
|
||||
## Canonical surfaces
|
||||
|
||||
- Weapon editor and client payload: `src/routes/isac/weapons/+page.svelte`
|
||||
- Weapon action, validation, persistence, and audit snapshot: `src/routes/isac/weapons/+page.server.ts`
|
||||
- Weapon categories: `src/lib/weapons/category-bonuses.ts`
|
||||
- Weapon mod-slot rules: `src/lib/weapons/mod-slots.ts`
|
||||
- Talent editor and classification: `src/routes/isac/talents/`
|
||||
- Talent screenshot workflow: `../../import-talent-screenshot/SKILL.md`
|
||||
- Icon validation and persistence: `src/lib/server/game-data-icons.ts`
|
||||
|
||||
Inspect these files before entry when fields or behavior may have changed.
|
||||
|
||||
## Request path
|
||||
|
||||
Prefer a direct authenticated request to the SvelteKit weapon action over editor interaction:
|
||||
|
||||
- Load or inspect `/isac/weapons` to obtain current talent, attribute, category, and registry data and confirm the authenticated session.
|
||||
- Construct `FormData` from the current contract in `+page.svelte` and `+page.server.ts`.
|
||||
- Preserve repeated relationship fields such as talent IDs and serialize structured fields such as mod slots exactly as the action expects.
|
||||
- Submit one `POST` request to `/isac/weapons?/save` with an accepted SvelteKit action response type.
|
||||
- Parse the action result, then query the registry and audit surfaces for readback.
|
||||
|
||||
Do not treat the action path as a public, version-stable REST API. Re-check the route implementation when it changes. Do not insert into the embedded database or call persistence utilities directly.
|
||||
|
||||
## Required standard-weapon data
|
||||
|
||||
- Unique name
|
||||
- Category: `Shotgun`, `Rifle`, `MMR`, `AR`, `LMG`, or `SMG`
|
||||
- Native tier: `T0`, `T1`, or `T2`
|
||||
- Damage type: `Blast`, `Pierce`, or `Shredding`
|
||||
- RPM, magazine size, reload seconds, accuracy, and stability
|
||||
- Damage minimum and maximum for every tier at or above the native tier
|
||||
- At least one existing Talent 1 and Talent 2 option
|
||||
- One or two unique Tactical Augmentation 01 attribute/value pairs
|
||||
|
||||
Weapon mod slots are optional evidence. An otherwise valid weapon with unknown slots may be saved and will remain `CONFIG REQUIRED`.
|
||||
|
||||
## Talent icon matching
|
||||
|
||||
Query the current weapon or talent registry data to obtain active Talent 1 and Talent 2 icon sets. Match within the correct slot only. Use a visual contact sheet when the set is large; opening the weapon editor is unnecessary.
|
||||
|
||||
These weapon talent grids display every talent twice: once for Tier I and once for Tier II. Treat each slot grid as an unordered set, group identical icons across the complete grid, and store each matching talent ID once. Tier variants may occupy different rows, have other talents between them, or appear in either order. Pair by icon artwork only. If a two-icon group has no unique registry match, request a close-up with its name and description and run `import-talent-screenshot` before continuing.
|
||||
|
||||
## Image preparation
|
||||
|
||||
Use the supplied weapon render as the source of truth. Remove the surrounding game interface and background without redesigning the weapon. Save the final asset in the workspace, include it in the server-action payload when the current weapon contract supports an image, and verify the resulting image URL.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-01-13
|
||||
|
||||
### Added
|
||||
|
||||
**Phrases (references/phrases.md)**
|
||||
- Throat-clearing: "Here's what I find interesting", "Here's the problem though"
|
||||
- Performative emphasis: "creeps in", "I promise", "They exist, I promise"
|
||||
- Telling instead of showing: "This is genuinely hard", "This is what leadership actually looks like"
|
||||
|
||||
**Structures (references/structures.md)**
|
||||
- Binary contrasts: "Not X. But Y.", "It's not this. It's that.", "stops being X and starts being Y"
|
||||
- Rhythm patterns: staccato fragmentation, dashes for dramatic pause, hedging as reassurance
|
||||
- Word patterns: absolute words (always, never, everyone, etc.), AI-overused intensifiers (deeply, truly, fundamentally, inherently, simply, literally, inevitably)
|
||||
|
||||
## 2026-01-12
|
||||
|
||||
- Restructured skill following Claude Code best practices (PR #1)
|
||||
- Split into SKILL.md and references/ folder
|
||||
|
||||
## 2025-01-12
|
||||
|
||||
- Initial release
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hardik Pandya
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Stop Slop
|
||||
|
||||
A skill for removing AI tells from prose.
|
||||
|
||||
<img width="3840" height="2160" alt="G-Yg4RVbIAAhVxW" src="https://github.com/user-attachments/assets/902afc15-1f40-4a9d-af24-8cd67afb8ebf" />
|
||||
|
||||
## What this is
|
||||
|
||||
AI writing has patterns. Predictable phrases, structures, rhythms. This skill teaches Claude (or any LLM) to catch and remove them.
|
||||
|
||||
## Skill Structure
|
||||
|
||||
```
|
||||
stop-slop/
|
||||
├── SKILL.md # Core instructions
|
||||
├── references/
|
||||
│ ├── phrases.md # Phrases to remove
|
||||
│ ├── structures.md # Structural patterns to avoid
|
||||
│ └── examples.md # Before/after transformations
|
||||
├── README.md
|
||||
└── LICENSE
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
**Claude Code:** Add this folder as a skill.
|
||||
|
||||
**Claude Projects:** Upload `SKILL.md` and reference files to project knowledge.
|
||||
|
||||
**Custom instructions:** Copy core rules from `SKILL.md`.
|
||||
|
||||
**API calls:** Include `SKILL.md` in your system prompt. Reference files load on demand.
|
||||
|
||||
## What it catches
|
||||
|
||||
**Banned phrases** - Throat-clearing openers, emphasis crutches, business jargon, all adverbs, vague declaratives, meta-commentary. See `references/phrases.md`.
|
||||
|
||||
**Structural clichés** - Binary contrasts, negative listings, dramatic fragmentation, rhetorical setups, false agency, narrator-from-a-distance voice, passive voice. See `references/structures.md`.
|
||||
|
||||
**Sentence-level rules** - No Wh- sentence starters, no em dashes, no staccato fragmentation, no lazy extremes, active voice required.
|
||||
|
||||
## Scoring
|
||||
|
||||
Rate 1-10 on each dimension:
|
||||
|
||||
| Dimension | Question |
|
||||
|-----------|----------|
|
||||
| Directness | Statements or announcements? |
|
||||
| Rhythm | Varied or metronomic? |
|
||||
| Trust | Respects reader intelligence? |
|
||||
| Authenticity | Sounds human? |
|
||||
| Density | Anything cuttable? |
|
||||
|
||||
Below 35/50: revise.
|
||||
|
||||
## Author
|
||||
|
||||
[Hardik Pandya](https://hvpandya.com)
|
||||
|
||||
## License
|
||||
|
||||
MIT. Use freely, share widely.
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
name: stop-slop
|
||||
description: Remove AI writing patterns from prose. Use when drafting, editing, or reviewing text to eliminate predictable AI tells.
|
||||
metadata:
|
||||
trigger: Writing prose, editing drafts, reviewing content for AI patterns
|
||||
author: Hardik Pandya (https://hvpandya.com)
|
||||
---
|
||||
|
||||
# Stop Slop
|
||||
|
||||
Eliminate predictable AI writing patterns from prose.
|
||||
|
||||
## Core Rules
|
||||
|
||||
1. **Cut filler phrases.** Remove throat-clearing openers, emphasis crutches, and all adverbs. See [references/phrases.md](references/phrases.md).
|
||||
|
||||
2. **Break formulaic structures.** Avoid binary contrasts, negative listings, dramatic fragmentation, rhetorical setups, false agency. See [references/structures.md](references/structures.md).
|
||||
|
||||
3. **Use active voice.** Every sentence needs a human subject doing something. No passive constructions. No inanimate objects performing human actions ("the complaint becomes a fix").
|
||||
|
||||
4. **Be specific.** No vague declaratives ("The reasons are structural"). Name the specific thing. No lazy extremes ("every," "always," "never") doing vague work.
|
||||
|
||||
5. **Put the reader in the room.** No narrator-from-a-distance voice. "You" beats "People." Specifics beat abstractions.
|
||||
|
||||
6. **Vary rhythm.** Mix sentence lengths. Two items beat three. End paragraphs differently. No em dashes.
|
||||
|
||||
7. **Trust readers.** State facts directly. Skip softening, justification, hand-holding.
|
||||
|
||||
8. **Cut quotables.** If it sounds like a pull-quote, rewrite it.
|
||||
|
||||
## Quick Checks
|
||||
|
||||
Before delivering prose:
|
||||
|
||||
- Any adverbs? Kill them.
|
||||
- Any passive voice? Find the actor, make them the subject.
|
||||
- Inanimate thing doing a human verb ("the decision emerges")? Name the person.
|
||||
- Sentence starts with a Wh- word? Restructure it.
|
||||
- Any "here's what/this/that" throat-clearing? Cut to the point.
|
||||
- Any "not X, it's Y" contrasts? State Y directly.
|
||||
- Three consecutive sentences match length? Break one.
|
||||
- Paragraph ends with punchy one-liner? Vary it.
|
||||
- Em-dash anywhere? Remove it.
|
||||
- Vague declarative ("The implications are significant")? Name the specific implication.
|
||||
- Narrator-from-a-distance ("Nobody designed this")? Put the reader in the scene.
|
||||
- Meta-joiners ("The rest of this essay...")? Delete. Let the essay move.
|
||||
|
||||
## Scoring
|
||||
|
||||
Rate 1-10 on each dimension:
|
||||
|
||||
| Dimension | Question |
|
||||
|-----------|----------|
|
||||
| Directness | Statements or announcements? |
|
||||
| Rhythm | Varied or metronomic? |
|
||||
| Trust | Respects reader intelligence? |
|
||||
| Authenticity | Sounds human? |
|
||||
| Density | Anything cuttable? |
|
||||
|
||||
Below 35/50: revise.
|
||||
|
||||
## Examples
|
||||
|
||||
See [references/examples.md](references/examples.md) for before/after transformations.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,59 @@
|
||||
# Before/After Examples
|
||||
|
||||
## Example 1: Throat-Clearing + Binary Contrast
|
||||
|
||||
**Before:**
|
||||
> "Here's the thing: building products is hard. Not because the technology is complex. Because people are complex. Let that sink in."
|
||||
|
||||
**After:**
|
||||
> "Building products is hard. Technology is manageable. People aren't."
|
||||
|
||||
**Changes:** Removed opener, binary contrast structure, and emphasis crutch. Direct statements.
|
||||
|
||||
---
|
||||
|
||||
## Example 2: Filler + Unnecessary Reassurance
|
||||
|
||||
**Before:**
|
||||
> "It turns out that most teams struggle with alignment. The uncomfortable truth is that nobody wants to admit they're confused. And that's okay."
|
||||
|
||||
**After:**
|
||||
> "Teams struggle with alignment. Nobody admits confusion."
|
||||
|
||||
**Changes:** Cut hedging ("most"), removed throat-clearing phrases, deleted permission-granting ending.
|
||||
|
||||
---
|
||||
|
||||
## Example 3: Business Jargon Stack
|
||||
|
||||
**Before:**
|
||||
> "In today's fast-paced landscape, we need to lean into discomfort and navigate uncertainty with clarity. This matters because your competition isn't waiting."
|
||||
|
||||
**After:**
|
||||
> "Move faster. Your competition is."
|
||||
|
||||
**Changes:** Eliminated jargon entirely. Core message in six words.
|
||||
|
||||
---
|
||||
|
||||
## Example 4: Dramatic Fragmentation
|
||||
|
||||
**Before:**
|
||||
> "Speed. Quality. Cost. You can only pick two. That's it. That's the tradeoff."
|
||||
|
||||
**After:**
|
||||
> "Speed, quality, cost—pick two."
|
||||
|
||||
**Changes:** Single sentence. No performative emphasis.
|
||||
|
||||
---
|
||||
|
||||
## Example 5: Rhetorical Setup
|
||||
|
||||
**Before:**
|
||||
> "What if I told you that the best teams don't optimize for productivity? Here's what I mean: they optimize for learning. Think about it."
|
||||
|
||||
**After:**
|
||||
> "The best teams optimize for learning, not productivity."
|
||||
|
||||
**Changes:** Direct claim. No rhetorical scaffolding.
|
||||
@@ -0,0 +1,128 @@
|
||||
# Phrases to Remove
|
||||
|
||||
## Throat-Clearing Openers
|
||||
|
||||
Remove these announcement phrases. State the content directly.
|
||||
|
||||
- "Here's the thing:"
|
||||
- "Here's what [X]"
|
||||
- "Here's this [X]"
|
||||
- "Here's that [X]"
|
||||
- "Here's why [X]"
|
||||
- "The uncomfortable truth is"
|
||||
- "It turns out"
|
||||
- "The real [X] is"
|
||||
- "Let me be clear"
|
||||
- "The truth is,"
|
||||
- "I'll say it again:"
|
||||
- "I'm going to be honest"
|
||||
- "Can we talk about"
|
||||
- "Here's what I find interesting"
|
||||
- "Here's the problem though"
|
||||
|
||||
Any "here's what/this/that" construction is throat-clearing before the point. Cut it and state the point.
|
||||
|
||||
## Emphasis Crutches
|
||||
|
||||
These add no meaning. Delete them.
|
||||
|
||||
- "Full stop." / "Period."
|
||||
- "Let that sink in."
|
||||
- "This matters because"
|
||||
- "Make no mistake"
|
||||
- "Here's why that matters"
|
||||
|
||||
## Business Jargon
|
||||
|
||||
Replace with plain language.
|
||||
|
||||
| Avoid | Use instead |
|
||||
|-------|-------------|
|
||||
| Navigate (challenges) | Handle, address |
|
||||
| Unpack (analysis) | Explain, examine |
|
||||
| Lean into | Accept, embrace |
|
||||
| Landscape (context) | Situation, field |
|
||||
| Game-changer | Significant, important |
|
||||
| Double down | Commit, increase |
|
||||
| Deep dive | Analysis, examination |
|
||||
| Take a step back | Reconsider |
|
||||
| Moving forward | Next, from now |
|
||||
| Circle back | Return to, revisit |
|
||||
| On the same page | Aligned, agreed |
|
||||
|
||||
## Adverbs
|
||||
|
||||
Kill all adverbs. No -ly words. No softeners, no intensifiers, no hedges.
|
||||
|
||||
Specific offenders:
|
||||
|
||||
- "really"
|
||||
- "just"
|
||||
- "literally"
|
||||
- "genuinely"
|
||||
- "honestly"
|
||||
- "simply"
|
||||
- "actually"
|
||||
- "deeply"
|
||||
- "truly"
|
||||
- "fundamentally"
|
||||
- "inherently"
|
||||
- "inevitably"
|
||||
- "interestingly"
|
||||
- "importantly"
|
||||
- "crucially"
|
||||
|
||||
Also cut these filler phrases:
|
||||
|
||||
- "At its core"
|
||||
- "In today's [X]"
|
||||
- "It's worth noting"
|
||||
- "At the end of the day"
|
||||
- "When it comes to"
|
||||
- "In a world where"
|
||||
- "The reality is"
|
||||
|
||||
## Meta-Commentary
|
||||
|
||||
Remove self-referential asides. The essay should move, not announce its own structure.
|
||||
|
||||
- "Hint:"
|
||||
- "Plot twist:" / "Spoiler:"
|
||||
- "You already know this, but"
|
||||
- "But that's another post"
|
||||
- "X is a feature, not a bug"
|
||||
- "Dressed up as"
|
||||
- "The rest of this essay explains..."
|
||||
- "Let me walk you through..."
|
||||
- "In this section, we'll..."
|
||||
- "As we'll see..."
|
||||
- "I want to explore..."
|
||||
|
||||
## Performative Emphasis
|
||||
|
||||
False intimacy or manufactured sincerity:
|
||||
|
||||
- "creeps in"
|
||||
- "I promise"
|
||||
- "They exist, I promise"
|
||||
|
||||
## Telling Instead of Showing
|
||||
|
||||
Announcing difficulty or significance rather than demonstrating it:
|
||||
|
||||
- "This is genuinely hard"
|
||||
- "This is what leadership actually looks like"
|
||||
- "This is what X actually looks like"
|
||||
- "actually matters"
|
||||
|
||||
## Vague Declaratives
|
||||
|
||||
Sentences that announce importance without naming the specific thing. Kill these.
|
||||
|
||||
- "The reasons are structural"
|
||||
- "The implications are significant"
|
||||
- "This is the deepest problem"
|
||||
- "The stakes are high"
|
||||
- "The consequences are real"
|
||||
|
||||
If a sentence says something is important/deep/structural without showing the specific thing, cut it or replace it with the specific thing.
|
||||
@@ -0,0 +1,134 @@
|
||||
# Structures to Avoid
|
||||
|
||||
## Binary Contrasts
|
||||
|
||||
These create false drama. State the point directly.
|
||||
|
||||
| Pattern | Problem |
|
||||
|---------|---------|
|
||||
| "Not because X. Because Y." / "Not because X, but because Y." | Telegraphed reversal |
|
||||
| "[X] isn't the problem. [Y] is." | Formulaic reframe |
|
||||
| "The answer isn't X. It's Y." | Predictable pivot |
|
||||
| "It feels like X. It's actually Y." | Setup/reveal cliche |
|
||||
| "The question isn't X. It's Y." | Rhetorical misdirection |
|
||||
| "Not X. But Y." / "not X, it's Y" / "isn't X, it's Y" | Mechanical contrast |
|
||||
| "It's not this. It's that." | Same formula, different words |
|
||||
| "stops being X and starts being Y" | False transformation arc |
|
||||
| "doesn't mean X, but actually Y" | Negation-then-assertion crutch |
|
||||
| "is about X but not Y" | False distinction |
|
||||
| "not just X but also Y" | Additive hedge |
|
||||
|
||||
**Instead:** State Y directly. "The problem is Y." "Y matters here." Drop the negation entirely.
|
||||
|
||||
## Negative Listing
|
||||
|
||||
Listing what something is *not* before revealing what it *is*. A rhetorical striptease.
|
||||
|
||||
| Pattern | Problem |
|
||||
|---------|---------|
|
||||
| "Not a X... Not a Y... A Z." | Dramatic buildup through negation |
|
||||
| "It wasn't X. It wasn't Y. It was Z." | Same structure, past tense |
|
||||
|
||||
**Instead:** State Z. The reader doesn't need the runway.
|
||||
|
||||
## Dramatic Fragmentation
|
||||
|
||||
Sentence fragments for emphasis read as manufactured profundity.
|
||||
|
||||
| Pattern | Problem |
|
||||
|---------|---------|
|
||||
| "[Noun]. That's it. That's the [thing]." | Performative simplicity |
|
||||
| "X. And Y. And Z." | Staccato drama |
|
||||
| "This unlocks something. [Word]." | Artificial revelation |
|
||||
|
||||
**Instead:** Complete sentences. Trust content over presentation.
|
||||
|
||||
## Rhetorical Setups
|
||||
|
||||
These announce insight rather than deliver it.
|
||||
|
||||
| Pattern | Problem |
|
||||
|---------|---------|
|
||||
| "What if [reframe]?" | Socratic posturing |
|
||||
| "Here's what I mean:" | Redundant preview |
|
||||
| "Think about it:" | Condescending prompt |
|
||||
| "And that's okay." | Unnecessary permission |
|
||||
|
||||
**Instead:** Make the point. Let readers draw conclusions.
|
||||
|
||||
## Formulaic Constructions
|
||||
|
||||
| Pattern | Problem |
|
||||
|---------|---------|
|
||||
| "By the time X, I was Y." | Narrative template |
|
||||
| "X that isn't Y" | Indirect. Say "X is broken" |
|
||||
|
||||
## False Agency
|
||||
|
||||
Giving inanimate things human verbs. Complaints don't "become" fixes. Bets don't "live or die." Decisions don't "emerge." A person does something to make those things happen. AI loves this because it avoids naming the actor.
|
||||
|
||||
| Pattern | Problem |
|
||||
|---------|---------|
|
||||
| "a complaint becomes a fix" | The complaint did nothing. Someone fixed it. |
|
||||
| "a bet lives or dies in days" | Bets don't have lifespans. Someone kills the project or ships it. |
|
||||
| "the decision emerges" | Decisions don't emerge. Someone decides. |
|
||||
| "the culture shifts" | Cultures don't shift on their own. People change behavior. |
|
||||
| "the conversation moves toward" | Conversations don't move. Someone steers. |
|
||||
| "the data tells us" | Data sits there. Someone reads it and draws a conclusion. |
|
||||
| "the market rewards" | Markets don't reward. Buyers pay for things. |
|
||||
|
||||
**Instead:** Name the human. "The team fixed it that week" beats "the complaint becomes a fix." If no specific person fits, use "you" to put the reader in the seat.
|
||||
|
||||
## Narrator-from-a-Distance
|
||||
|
||||
Floating above the scene instead of putting the reader in it.
|
||||
|
||||
| Pattern | Problem |
|
||||
|---------|---------|
|
||||
| "Nobody designed this." | Disembodied observation |
|
||||
| "This happens because..." | Lecturer voice |
|
||||
| "This is why..." | Same |
|
||||
| "People tend to..." | Armchair sociologist |
|
||||
|
||||
**Instead:** Put the reader in the room. "You don't sit down one day and decide to..." beats "Nobody designed this."
|
||||
|
||||
## Passive Voice
|
||||
|
||||
Every sentence needs a subject doing something. Passive voice hides the actor and drains energy.
|
||||
|
||||
| Pattern | Fix |
|
||||
|---------|-----|
|
||||
| "X was created" | Name who created it |
|
||||
| "It is believed that" | Name who believes it |
|
||||
| "Mistakes were made" | Name who made them |
|
||||
| "The decision was reached" | Name who decided |
|
||||
|
||||
**Instead:** Find the actor. Put them at the front of the sentence.
|
||||
|
||||
## Sentence Starters to Avoid
|
||||
|
||||
| Pattern | Fix |
|
||||
|---------|-----|
|
||||
| Sentences starting with What, When, Where, Which, Who, Why, How | Restructure. Lead with the subject or the verb. |
|
||||
| Paragraphs starting with "So" | Start with content |
|
||||
| Sentences starting with "Look," | Remove |
|
||||
|
||||
Wh- openers become a crutch. "What makes this hard is..." becomes "The constraint is..." or better, name the specific constraint.
|
||||
|
||||
## Rhythm Patterns
|
||||
|
||||
| Pattern | Fix |
|
||||
|---------|-----|
|
||||
| Three-item lists | Use two items or one |
|
||||
| Questions answered immediately | Let questions breathe or cut them |
|
||||
| Every paragraph ends punchily | Vary endings |
|
||||
| Em-dashes | Remove. Use commas or periods. No em dashes at all. |
|
||||
| Staccato fragmentation | Don't stack short punchy sentences |
|
||||
| "Not always. Not perfectly." | Hedging disguised as reassurance |
|
||||
|
||||
## Word Patterns
|
||||
|
||||
| Pattern | Problem |
|
||||
|---------|---------|
|
||||
| Lazy extremes (every, always, never, everyone, everybody, nobody) | False authority. Use specifics instead of sweeping claims. |
|
||||
| All adverbs (-ly words, "really," "just," "literally," "genuinely," "honestly," "simply," "actually") | Empty emphasis. See phrases.md for full list. |
|
||||
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
name: webapp-testing
|
||||
description: Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# Web Application Testing
|
||||
|
||||
To test local web applications, write native Python Playwright scripts.
|
||||
|
||||
**Helper Scripts Available**:
|
||||
- `scripts/with_server.py` - Manages server lifecycle (supports multiple servers)
|
||||
|
||||
**Always run scripts with `--help` first** to see usage. DO NOT read the source until you try running the script first and find that a customized solution is abslutely necessary. These scripts can be very large and thus pollute your context window. They exist to be called directly as black-box scripts rather than ingested into your context window.
|
||||
|
||||
## Decision Tree: Choosing Your Approach
|
||||
|
||||
```
|
||||
User task → Is it static HTML?
|
||||
├─ Yes → Read HTML file directly to identify selectors
|
||||
│ ├─ Success → Write Playwright script using selectors
|
||||
│ └─ Fails/Incomplete → Treat as dynamic (below)
|
||||
│
|
||||
└─ No (dynamic webapp) → Is the server already running?
|
||||
├─ No → Run: python scripts/with_server.py --help
|
||||
│ Then use the helper + write simplified Playwright script
|
||||
│
|
||||
└─ Yes → Reconnaissance-then-action:
|
||||
1. Navigate and wait for networkidle
|
||||
2. Take screenshot or inspect DOM
|
||||
3. Identify selectors from rendered state
|
||||
4. Execute actions with discovered selectors
|
||||
```
|
||||
|
||||
## Example: Using with_server.py
|
||||
|
||||
To start a server, run `--help` first, then use the helper:
|
||||
|
||||
**Single server:**
|
||||
```bash
|
||||
python scripts/with_server.py --server "npm run dev" --port 5173 -- python your_automation.py
|
||||
```
|
||||
|
||||
**Multiple servers (e.g., backend + frontend):**
|
||||
```bash
|
||||
python scripts/with_server.py \
|
||||
--server "cd backend && python server.py" --port 3000 \
|
||||
--server "cd frontend && npm run dev" --port 5173 \
|
||||
-- python your_automation.py
|
||||
```
|
||||
|
||||
To create an automation script, include only Playwright logic (servers are managed automatically):
|
||||
```python
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True) # Always launch chromium in headless mode
|
||||
page = browser.new_page()
|
||||
page.goto('http://localhost:5173') # Server already running and ready
|
||||
page.wait_for_load_state('networkidle') # CRITICAL: Wait for JS to execute
|
||||
# ... your automation logic
|
||||
browser.close()
|
||||
```
|
||||
|
||||
## Reconnaissance-Then-Action Pattern
|
||||
|
||||
1. **Inspect rendered DOM**:
|
||||
```python
|
||||
page.screenshot(path='/tmp/inspect.png', full_page=True)
|
||||
content = page.content()
|
||||
page.locator('button').all()
|
||||
```
|
||||
|
||||
2. **Identify selectors** from inspection results
|
||||
|
||||
3. **Execute actions** using discovered selectors
|
||||
|
||||
## Common Pitfall
|
||||
|
||||
❌ **Don't** inspect the DOM before waiting for `networkidle` on dynamic apps
|
||||
✅ **Do** wait for `page.wait_for_load_state('networkidle')` before inspection
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Use bundled scripts as black boxes** - To accomplish a task, consider whether one of the scripts available in `scripts/` can help. These scripts handle common, complex workflows reliably without cluttering the context window. Use `--help` to see usage, then invoke directly.
|
||||
- Use `sync_playwright()` for synchronous scripts
|
||||
- Always close the browser when done
|
||||
- Use descriptive selectors: `text=`, `role=`, CSS selectors, or IDs
|
||||
- Add appropriate waits: `page.wait_for_selector()` or `page.wait_for_timeout()`
|
||||
|
||||
## Reference Files
|
||||
|
||||
- **examples/** - Examples showing common patterns:
|
||||
- `element_discovery.py` - Discovering buttons, links, and inputs on a page
|
||||
- `static_html_automation.py` - Using file:// URLs for local HTML
|
||||
- `console_logging.py` - Capturing console logs during automation
|
||||
@@ -0,0 +1,35 @@
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
# Example: Capturing console logs during browser automation
|
||||
|
||||
url = 'http://localhost:5173' # Replace with your URL
|
||||
|
||||
console_logs = []
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
page = browser.new_page(viewport={'width': 1920, 'height': 1080})
|
||||
|
||||
# Set up console log capture
|
||||
def handle_console_message(msg):
|
||||
console_logs.append(f"[{msg.type}] {msg.text}")
|
||||
print(f"Console: [{msg.type}] {msg.text}")
|
||||
|
||||
page.on("console", handle_console_message)
|
||||
|
||||
# Navigate to page
|
||||
page.goto(url)
|
||||
page.wait_for_load_state('networkidle')
|
||||
|
||||
# Interact with the page (triggers console logs)
|
||||
page.click('text=Dashboard')
|
||||
page.wait_for_timeout(1000)
|
||||
|
||||
browser.close()
|
||||
|
||||
# Save console logs to file
|
||||
with open('/mnt/user-data/outputs/console.log', 'w') as f:
|
||||
f.write('\n'.join(console_logs))
|
||||
|
||||
print(f"\nCaptured {len(console_logs)} console messages")
|
||||
print(f"Logs saved to: /mnt/user-data/outputs/console.log")
|
||||
@@ -0,0 +1,40 @@
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
# Example: Discovering buttons and other elements on a page
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
page = browser.new_page()
|
||||
|
||||
# Navigate to page and wait for it to fully load
|
||||
page.goto('http://localhost:5173')
|
||||
page.wait_for_load_state('networkidle')
|
||||
|
||||
# Discover all buttons on the page
|
||||
buttons = page.locator('button').all()
|
||||
print(f"Found {len(buttons)} buttons:")
|
||||
for i, button in enumerate(buttons):
|
||||
text = button.inner_text() if button.is_visible() else "[hidden]"
|
||||
print(f" [{i}] {text}")
|
||||
|
||||
# Discover links
|
||||
links = page.locator('a[href]').all()
|
||||
print(f"\nFound {len(links)} links:")
|
||||
for link in links[:5]: # Show first 5
|
||||
text = link.inner_text().strip()
|
||||
href = link.get_attribute('href')
|
||||
print(f" - {text} -> {href}")
|
||||
|
||||
# Discover input fields
|
||||
inputs = page.locator('input, textarea, select').all()
|
||||
print(f"\nFound {len(inputs)} input fields:")
|
||||
for input_elem in inputs:
|
||||
name = input_elem.get_attribute('name') or input_elem.get_attribute('id') or "[unnamed]"
|
||||
input_type = input_elem.get_attribute('type') or 'text'
|
||||
print(f" - {name} ({input_type})")
|
||||
|
||||
# Take screenshot for visual reference
|
||||
page.screenshot(path='/tmp/page_discovery.png', full_page=True)
|
||||
print("\nScreenshot saved to /tmp/page_discovery.png")
|
||||
|
||||
browser.close()
|
||||
@@ -0,0 +1,33 @@
|
||||
from playwright.sync_api import sync_playwright
|
||||
import os
|
||||
|
||||
# Example: Automating interaction with static HTML files using file:// URLs
|
||||
|
||||
html_file_path = os.path.abspath('path/to/your/file.html')
|
||||
file_url = f'file://{html_file_path}'
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
page = browser.new_page(viewport={'width': 1920, 'height': 1080})
|
||||
|
||||
# Navigate to local HTML file
|
||||
page.goto(file_url)
|
||||
|
||||
# Take screenshot
|
||||
page.screenshot(path='/mnt/user-data/outputs/static_page.png', full_page=True)
|
||||
|
||||
# Interact with elements
|
||||
page.click('text=Click Me')
|
||||
page.fill('#name', 'John Doe')
|
||||
page.fill('#email', 'john@example.com')
|
||||
|
||||
# Submit form
|
||||
page.click('button[type="submit"]')
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
# Take final screenshot
|
||||
page.screenshot(path='/mnt/user-data/outputs/after_submit.png', full_page=True)
|
||||
|
||||
browser.close()
|
||||
|
||||
print("Static HTML automation completed!")
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Start one or more servers, wait for them to be ready, run a command, then clean up.
|
||||
|
||||
Usage:
|
||||
# Single server
|
||||
python scripts/with_server.py --server "npm run dev" --port 5173 -- python automation.py
|
||||
python scripts/with_server.py --server "npm start" --port 3000 -- python test.py
|
||||
|
||||
# Multiple servers
|
||||
python scripts/with_server.py \
|
||||
--server "cd backend && python server.py" --port 3000 \
|
||||
--server "cd frontend && npm run dev" --port 5173 \
|
||||
-- python test.py
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import socket
|
||||
import time
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
def is_server_ready(port, timeout=30):
|
||||
"""Wait for server to be ready by polling the port."""
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
with socket.create_connection(('localhost', port), timeout=1):
|
||||
return True
|
||||
except (socket.error, ConnectionRefusedError):
|
||||
time.sleep(0.5)
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Run command with one or more servers')
|
||||
parser.add_argument('--server', action='append', dest='servers', required=True, help='Server command (can be repeated)')
|
||||
parser.add_argument('--port', action='append', dest='ports', type=int, required=True, help='Port for each server (must match --server count)')
|
||||
parser.add_argument('--timeout', type=int, default=30, help='Timeout in seconds per server (default: 30)')
|
||||
parser.add_argument('command', nargs=argparse.REMAINDER, help='Command to run after server(s) ready')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Remove the '--' separator if present
|
||||
if args.command and args.command[0] == '--':
|
||||
args.command = args.command[1:]
|
||||
|
||||
if not args.command:
|
||||
print("Error: No command specified to run")
|
||||
sys.exit(1)
|
||||
|
||||
# Parse server configurations
|
||||
if len(args.servers) != len(args.ports):
|
||||
print("Error: Number of --server and --port arguments must match")
|
||||
sys.exit(1)
|
||||
|
||||
servers = []
|
||||
for cmd, port in zip(args.servers, args.ports):
|
||||
servers.append({'cmd': cmd, 'port': port})
|
||||
|
||||
server_processes = []
|
||||
|
||||
try:
|
||||
# Start all servers
|
||||
for i, server in enumerate(servers):
|
||||
print(f"Starting server {i+1}/{len(servers)}: {server['cmd']}")
|
||||
|
||||
# Use shell=True to support commands with cd and &&
|
||||
process = subprocess.Popen(
|
||||
server['cmd'],
|
||||
shell=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE
|
||||
)
|
||||
server_processes.append(process)
|
||||
|
||||
# Wait for this server to be ready
|
||||
print(f"Waiting for server on port {server['port']}...")
|
||||
if not is_server_ready(server['port'], timeout=args.timeout):
|
||||
raise RuntimeError(f"Server failed to start on port {server['port']} within {args.timeout}s")
|
||||
|
||||
print(f"Server ready on port {server['port']}")
|
||||
|
||||
print(f"\nAll {len(servers)} server(s) ready")
|
||||
|
||||
# Run the command
|
||||
print(f"Running: {' '.join(args.command)}\n")
|
||||
result = subprocess.run(args.command)
|
||||
sys.exit(result.returncode)
|
||||
|
||||
finally:
|
||||
# Clean up all servers
|
||||
print(f"\nStopping {len(server_processes)} server(s)...")
|
||||
for i, process in enumerate(server_processes):
|
||||
try:
|
||||
process.terminate()
|
||||
process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
process.wait()
|
||||
print(f"Server {i+1} stopped")
|
||||
print("All servers stopped")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user