diff --git a/.github/prompts/portfolio-project-sync.prompt.yml b/.github/prompts/portfolio-project-sync.prompt.yml index 03beb7f..90069b8 100644 --- a/.github/prompts/portfolio-project-sync.prompt.yml +++ b/.github/prompts/portfolio-project-sync.prompt.yml @@ -11,6 +11,11 @@ messages: - Preserve source/live/demo links when present in the update. - Mark risk high if the update asks to change layout, dependencies, secrets, workflow files, or anything outside project content. - Mark needs_review when the update is too vague, promotional, or missing enough project details. + - Only extend the existing Projects card list. Do not recreate, redesign, reorganize, or add UI/UX behavior. + - Never create new UI categories, filters, tabs, sections, buttons, menus, sorting controls, grouping controls, or layout changes. + - Tags are card metadata only. They must not be treated as UI controls or filter categories. + - Use only approved existing project tags: Python, TensorFlow, APIs, ML, Healthcare, Java, Spring Boot, Microservices, MongoDB, React, API, Frontend, NLP, Recommender, Streamlit, Computer Vision, Regression, Deep Learning, Data, Security, GUI, HTML, CSS, JavaScript, GitHub Pages. + - Map specific technologies to approved tags instead of inventing new tags. For example, Scikit-learn maps to ML, Pandas/NumPy/CSV/Mermaid maps to Data, TF-IDF maps to NLP, cosine similarity maps to Recommender. - For new projects, use empty string for image so the workflow can capture the live/demo site thumbnail. - For existing project updates, preserve or reuse the existing image only when it still clearly fits. - Use featured true for newly added projects so they appear at the top by default. @@ -63,7 +68,10 @@ jsonSchema: |- "impact": { "type": "string" }, "tags": { "type": "array", - "items": { "type": "string" }, + "items": { + "type": "string", + "enum": ["Python", "TensorFlow", "APIs", "ML", "Healthcare", "Java", "Spring Boot", "Microservices", "MongoDB", "React", "API", "Frontend", "NLP", "Recommender", "Streamlit", "Computer Vision", "Regression", "Deep Learning", "Data", "Security", "GUI", "HTML", "CSS", "JavaScript", "GitHub Pages"] + }, "maxItems": 8 }, "featured": { "type": "boolean" }, diff --git a/portfolio-v2/docs/portfolio-sync.md b/portfolio-v2/docs/portfolio-sync.md index dce1508..756bc1b 100644 --- a/portfolio-v2/docs/portfolio-sync.md +++ b/portfolio-v2/docs/portfolio-sync.md @@ -52,6 +52,7 @@ The workflow: - Sends the current projects and pasted update to GitHub Models. - Converts the update into structured project data. - Updates an existing project instead of creating a duplicate when the title, a similar title, or a project URL matches an existing card. +- Keeps the Projects UI card-only. The sync can add or update cards, but it must not introduce filters, tabs, sections, buttons, or layout behavior. - Prepares a project thumbnail by reusing an existing local image, downloading `card_image_url`, or capturing a screenshot from the live/demo URL. - Applies an add/update to the project list. - Places newly added projects in the featured/top project row by default unless the update explicitly says not to feature them. @@ -80,6 +81,8 @@ You can switch it to another model available in your GitHub Models catalog. The workflow treats dependency, workflow, secret, layout, and broad code changes as high risk. High-risk or unclear updates do not get merged automatically. Missing or failed project thumbnails are also blocked. Instead, the workflow creates a review issue and can send an email alert. +Project sync must not introduce new UI controls. Generated tags are restricted to the existing portfolio taxonomy and remain card metadata only. The Projects section intentionally renders project cards without generated filters, tabs, categories, or sorting controls. Specific technologies are mapped into broad existing tags such as `ML`, `Data`, `NLP`, and `Recommender`. + ## Preview Before Merge Generated project PRs can deploy preview automatically from the sync workflow. Keep `deploy_preview=true` for the normal one-click path. diff --git a/portfolio-v2/scripts/applyPortfolioProjectUpdate.mjs b/portfolio-v2/scripts/applyPortfolioProjectUpdate.mjs index 1e0c174..f365b9c 100644 --- a/portfolio-v2/scripts/applyPortfolioProjectUpdate.mjs +++ b/portfolio-v2/scripts/applyPortfolioProjectUpdate.mjs @@ -6,6 +6,49 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); const projectsDataPath = resolve(__dirname, "../src/projects.json"); const summaryPath = resolve(__dirname, "../portfolio-sync-summary.md"); const defaultProjectImage = "images/Projects/headlines.png"; +const approvedProjectTags = [ + "Python", + "TensorFlow", + "APIs", + "ML", + "Healthcare", + "Java", + "Spring Boot", + "Microservices", + "MongoDB", + "React", + "API", + "Frontend", + "NLP", + "Recommender", + "Streamlit", + "Computer Vision", + "Regression", + "Deep Learning", + "Data", + "Security", + "GUI", + "HTML", + "CSS", + "JavaScript", + "GitHub Pages", +]; +const approvedProjectTagMap = new Map(approvedProjectTags.map((tag) => [tag.toLowerCase(), tag])); +const projectTagAliases = new Map([ + ["scikit-learn", "ML"], + ["sklearn", "ML"], + ["pandas", "Data"], + ["numpy", "Data"], + ["csv", "Data"], + ["dataset", "Data"], + ["tf-idf", "NLP"], + ["tfidf", "NLP"], + ["cosine similarity", "Recommender"], + ["similarity", "Recommender"], + ["recommendation", "Recommender"], + ["visualization", "Data"], + ["mermaid", "Data"], +]); const responsePath = process.argv[2]; @@ -166,7 +209,21 @@ function normalizeStringArray(value) { return []; } - return value.map(clean).filter(Boolean).slice(0, 8); + return [...new Set(value.map(normalizeProjectTag).filter(Boolean))].slice(0, 8); +} + +function normalizeProjectTag(value) { + const tag = clean(value); + if (!tag) { + return ""; + } + + const directMatch = approvedProjectTagMap.get(tag.toLowerCase()); + if (directMatch) { + return directMatch; + } + + return projectTagAliases.get(tag.toLowerCase()) || ""; } function normalizeLinks(value) { diff --git a/portfolio-v2/src/App.jsx b/portfolio-v2/src/App.jsx index 2bdc824..aeb5ae0 100644 --- a/portfolio-v2/src/App.jsx +++ b/portfolio-v2/src/App.jsx @@ -14,7 +14,7 @@ import { Sparkles, X, } from "lucide-react"; -import { useMemo, useState } from "react"; +import { useState } from "react"; import { certifications, education, @@ -31,19 +31,9 @@ const defaultProjectImage = "images/Projects/headlines.png"; function App() { const [menuOpen, setMenuOpen] = useState(false); - const [activeFilter, setActiveFilter] = useState("All"); const featuredProjects = projects.filter((project) => project.featured); const libraryProjects = projects.filter((project) => !project.featured); - const filters = useMemo(() => { - const tags = projects.flatMap((project) => project.tags); - return ["All", ...Array.from(new Set(tags)).slice(0, 9)]; - }, []); - - const shownProjects = libraryProjects.filter( - (project) => activeFilter === "All" || project.tags.includes(activeFilter) - ); - return (
@@ -169,21 +159,8 @@ function App() { ))}
-
- {filters.map((filter) => ( - - ))} -
-
- {shownProjects.map((project) => ( + {libraryProjects.map((project) => ( ))}
diff --git a/portfolio-v2/src/styles.css b/portfolio-v2/src/styles.css index 71389a3..a72be97 100644 --- a/portfolio-v2/src/styles.css +++ b/portfolio-v2/src/styles.css @@ -527,30 +527,6 @@ a { text-decoration: none; } -.project-tools { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin: 34px 0 0; -} - -.filter { - background: transparent; - border: 1px solid var(--line); - border-radius: 999px; - color: var(--ink-soft); - cursor: pointer; - font-weight: 800; - min-height: 38px; - padding: 0 13px; -} - -.filter.active { - background: var(--dark); - border-color: var(--dark); - color: white; -} - .section-split { align-items: start; display: grid;