From e1c6e8771b4ae5f991f4f3518a768adf56fec582 Mon Sep 17 00:00:00 2001 From: vikramsh2002 Date: Tue, 2 Jun 2026 12:17:43 +0530 Subject: [PATCH 1/2] Keep project filters stable during sync --- .../prompts/portfolio-project-sync.prompt.yml | 8 ++- portfolio-v2/docs/portfolio-sync.md | 3 + .../scripts/applyPortfolioProjectUpdate.mjs | 59 ++++++++++++++++++- portfolio-v2/src/App.jsx | 21 ++++--- 4 files changed, 82 insertions(+), 9 deletions(-) diff --git a/.github/prompts/portfolio-project-sync.prompt.yml b/.github/prompts/portfolio-project-sync.prompt.yml index 03beb7f..342e982 100644 --- a/.github/prompts/portfolio-project-sync.prompt.yml +++ b/.github/prompts/portfolio-project-sync.prompt.yml @@ -11,6 +11,9 @@ 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. + - Never create new UI categories, filter names, sections, buttons, or layout changes. + - 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 +66,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..722bc27 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 project filter buttons stable by allowing only the approved existing tag taxonomy. - 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 the visible project filter row is fixed in the React UI. 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..c356a44 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, @@ -28,6 +28,18 @@ import { const asset = (path) => `${import.meta.env.BASE_URL}${path}`; const resumeHref = asset("resume/Vikram_Sharma_Python_Backend_Engineer.pdf"); const defaultProjectImage = "images/Projects/headlines.png"; +const projectFilters = [ + "All", + "Python", + "TensorFlow", + "APIs", + "ML", + "Healthcare", + "Java", + "Spring Boot", + "Microservices", + "MongoDB", +]; function App() { const [menuOpen, setMenuOpen] = useState(false); @@ -35,11 +47,6 @@ function App() { 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) ); @@ -170,7 +177,7 @@ function App() {
- {filters.map((filter) => ( + {projectFilters.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;