Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/prompts/portfolio-project-sync.prompt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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" },
Expand Down
3 changes: 3 additions & 0 deletions portfolio-v2/docs/portfolio-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
59 changes: 58 additions & 1 deletion portfolio-v2/scripts/applyPortfolioProjectUpdate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down Expand Up @@ -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) {
Expand Down
27 changes: 2 additions & 25 deletions portfolio-v2/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
Sparkles,
X,
} from "lucide-react";
import { useMemo, useState } from "react";
import { useState } from "react";
import {
certifications,
education,
Expand All @@ -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 (
<div className="app-shell">
<Header menuOpen={menuOpen} setMenuOpen={setMenuOpen} />
Expand Down Expand Up @@ -169,21 +159,8 @@ function App() {
))}
</div>

<div className="project-tools" aria-label="Project filters">
{filters.map((filter) => (
<button
className={filter === activeFilter ? "filter active" : "filter"}
key={filter}
onClick={() => setActiveFilter(filter)}
type="button"
>
{filter}
</button>
))}
</div>

<div className="project-grid">
{shownProjects.map((project) => (
{libraryProjects.map((project) => (
<ProjectCard key={project.title} project={project} />
))}
</div>
Expand Down
24 changes: 0 additions & 24 deletions portfolio-v2/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading