אייקוני שפות התכנות
- summary:
כל קובץ ב-Web App מוצג עם אייקון שמייצג את שפת התכנות שלו. עד אוגוסט 2026 אלה היו אמוג’ים (🐍 לפייתון, 📜 ל-JavaScript); היום אלה אייקונים מצוירים בסגנון אחיד — אריח ריבועי עם גרדיאנט וסימן לבן.
המסמך מתאר איך המערכת בנויה, איך מוסיפים אייקון, ובאילו מלכודות כבר נתקלנו.
למה זה נבנה כמערכת ולא כהחלפה נקודתית
לפני השינוי, אייקוני השפה יוצרו בחמישה מקומות נפרדים עם כיסוי שונה:
המקום |
כמה שפות כיסה |
|---|---|
|
28 |
|
8 |
מפה מקומית ב- |
חלקי |
מפה מקומית ב- |
חלקי |
מפות ב- |
חלקי |
התוצאה הייתה שאותו קובץ קיבל אייקון אחד בדף הקבצים ואייקון אחר בחיפוש הגלובלי. החלפה נקודתית בכל אחד מהחמישה הייתה משכפלת את הבעיה במקום לפתור אותה, ולכן המערכת נבנתה סביב מקור אמת אחד.
המבנה
FEATURE_SUGGESTIONS/New_Icons/*.svg 28 קבצי מקור, אחד לכל שפה
│
│ scripts/build_lang_sprite.py
▼
webapp/templates/components/lang_sprite.html ספרייט מוזרק ב-base.html
│
├──► lang_icon() ב-webapp/app.py — צד השרת (Jinja)
└──► window.langIcon() ב-base.html — צד הלקוח
שני הצדדים ניזונים מאותם נתונים: השרת מזריק אותם ל-window.LANG_ICON_DATA,
ולכן אין מפת שפות משוכפלת בקוד ה-JS.
צד השרת
lang_icon() רשומה כגלובל של Jinja וזמינה בכל תבנית בלי import:
{{ lang_icon(file.language, LANG_ICON_SIZES.file_row) }}
חתימה: lang_icon(language, size=32, css_class='', decorative=True).
סדר העדיפויות בבחירת האייקון:
יש אייקון מצויר לשפה — מחזירים אותו.
אין אייקון אבל יש לשפה סמל ייחודי במפת האמוג’י (למשל 🔒 לקובץ בינארי) — שומרים עליו במקום לאבד מידע.
שפה שאינה מוכרת בכלל — אייקון ה-
text, כדי שלא תתקבל תערובת של אמוג’י ואייקונים באותה רשימה.
צד הלקוח
שתי פונקציות, שתיהן ב-base.html:
window.langIcon(language, size, decorative)— מחזירה מחרוזת HTML, לקוד שמרכיב תבנית טקסטואלית.window.langIconEl(language, size, decorative)— מחזירה אלמנט DOM בנוי ב-createElementNS. עדיפה בכל מקום שממילא בונה DOM, כי אז אין צורך ב-innerHTMLבכלל.
שתיהן נגזרות מאותה החלטה פנימית, כדי שלא יתפצלו.
גדלים
הגדלים מרוכזים ב-LANG_ICON_SIZES ב-webapp/app.py, ונחשפים גם ל-Jinja
וגם ללקוח דרך window.LANG_ICON_DATA.sizes:
שם |
גודל |
היכן |
|---|---|---|
|
44px |
דף הקבצים, כרטיסים נעוצים, עמוד הקובץ |
|
32px |
דשבורד, קבצים משותפים, סל מיחזור, מודאלים |
|
28px |
אירועי קבצים בטיימליין הפעילות |
|
28px |
תוצאות החיפוש הגלובלי |
|
22px |
ספריית הסניפטים |
חשוב
אין להשתמש במספר קשיח בקריאה ל-lang_icon בתבנית. יש בדיקה שנופלת על כך
(test_templates_use_named_sizes_not_numbers). הסיבה אינה אסתטית: כשהמספרים
ישבו בנפרד, אותו מסך קיבל שני גדלים שונים בשני מסלולי רינדור.
למה 44px ולא 32px
האייקונים הוחלפו בתחילה באותו גודל שהיה לאמוג’י, והתוצאה נראתה קטנה יותר. מדידה בדפדפן הראתה שתי סיבות שמצטברות:
אמוג’י ב-
font-size: 32pxמצויר בפועל ב-35.5px — הגליף חורג מהמסגרת שהוגדרה לו. אייקון SVG ב-32px מצויר ב-30px בדיוק, כי ל-viewBoxיש שוליים פנימיים.והעיקר: הסימן הלבן בתוך אריח של 32px נמדד 15×15 פיקסלים, מול 35.5px של אמוג’י שהצורה שלו ממלאת את כל שטחו. הסימן שהעין מזהה קטן פי 2.4.
השני הוא תכונה של האריח ולא באג — יש לו נשימה מכוונת סביב הסימן. המסקנה המעשית: החלפת אמוג’י באריח דורשת הגדלה, ולא החלפה 1:1.
הוספה או שינוי של אייקון
# 1. ערכו או הוסיפו קובץ תחת FEATURE_SUGGESTIONS/New_Icons/<slug>.svg
# 2. בנו מחדש את הספרייט
python scripts/build_lang_sprite.py
# 3. אם נוספה שפה חדשה — הוסיפו את ה-slug ל-LANG_ICON_SLUGS ב-webapp/app.py
# אימות שהספרייט מסונכרן, בלי לכתוב:
python scripts/build_lang_sprite.py --check
הספרייט נגזר מקבצי המקור ולא נערך ידנית. הסיבה קונקרטית: כשארבעת האייקונים האחרונים נוספו לתיקייה, הספרייט נשאר עם 24 ואף אחד לא שם לב עד שנבדק.
מבנה קובץ אייקון
כל אייקון הוא viewBox="0 0 64 64" ובנוי מארבע שכבות. הדוגמה למטה היא
אייקון ה-markdown בפועל, פרט ל-SLUG שיש להחליף בשם השפה. שני ערכי
ה-stop-color הם הגוון הבהיר והכהה של האריח — כל אייקון בוחר זוג משלו:
<defs>
<linearGradient id="g-SLUG" x1="0" y1="0" x2=".35" y2="1">
<stop offset="0" stop-color="#8fa6c9"/><stop offset="1" stop-color="#3b5378"/>
</linearGradient>
<g id="m-SLUG"> <!-- המוטיב, ב-currentColor --> </g>
</defs>
<rect x="2" y="2" width="60" height="60" rx="15" fill="url(#g-SLUG)"/>
<rect x="3" y="3" width="58" height="58" rx="14" fill="none"
stroke="#fff" stroke-opacity=".28" stroke-width="1.6"/>
<use href="#m-SLUG" transform="translate(0 1.8)" color="#141010" opacity=".22"/>
<use href="#m-SLUG" color="#ffffff"/>
אזהרה
ה-IDs חייבים לכלול את שם השפה (g-python, לא g). בגרסה הראשונה כל 24
הקבצים השתמשו ב-id="g" ו-id="m", ולכן הטמעת שניים מהם inline באותו
דף גרמה לשני האייקונים לקבל את הגרדיאנט של הראשון.
שמות נרדפים
LANG_ICON_ALIASES ממפה שפות שחולקות אייקון: shell/sh/zsh ל-bash,
scss/sass/less ל-css, node/nodejs ל-javascript ועוד.
זה חוסך ציור אייקון כפול.
הערה
ב-snippets.js יש נרמול שפות נפרד, לצורכי הדגשת תחביר. שתי המפות אינן
זהות בכוונה: scss חולק אייקון עם css, אבל חייב להישאר scss כדי
ש-highlight.js יצבע נכון. בדיקה מוודאת שהן לא סותרות זו את זו.
נגישות
האייקון מוסתר מקוראי מסך כברירת מחדל (aria-hidden="true"). בכל מקום שהוא
מוצג בו מופיע לצדו שם הקובץ עם הסיומת או תגית השפה, כך שהקראה נוספת רק מכפילה
את המידע — ובשורות השפות בדשבורד היא גרמה להקראת השם פעמיים.
כשהאייקון הוא הסימן היחיד לשפה, העבירו decorative=False כדי לקבל
role="img" ו-aria-label.
מלכודות שכבר נתקלנו בהן
הסתרת הספרייט
הספרייט חייב להיות מוסתר עם position:absolute;width:0;height:0;overflow:hidden
ולא עם display:none. עם display:none הדפדפן אינו מרנדר את הגרדיאנטים
שב-<defs>, וכל האריחים יוצאים שקופים לגמרי — האייקון מופיע בלי צבע.
יש בדיקה שמונעת חזרה לאחור.
הטמעה inline ולא כקובץ חיצוני
<use href="/static/sprite.svg#lang-python"> — הפניה לקובץ SVG חיצוני — אינה
נתמכת ב-Safari/iOS בלי polyfill. לכן הספרייט מוזרק inline ב-base.html.
העלות: כ-22KB לכל דף, כ-5KB אחרי gzip, בתמורה לאפס בקשות רשת.
שני מסלולי רינדור לאותו מסך
הטיימליין בדשבורד נבנה בשני מסלולים: רינדור ראשוני ב-app.py, ו“טען עוד“
דרך /api/dashboard/activity/files. כשרק הראשון עבר לאייקון מצויר, לחיצה על
”טען עוד“ הכניסה אמוג’ים לרשימה שכבר הכילה אייקונים.
הפתרון אינו העתקת הקריאה לשני המקומות. האירוע נושא את שם השפה בשדה icon_lang,
וצד הלקוח בונה ממנו את האייקון עם langIconEl — אותה פונקציה שבונה כל אייקון
אחר. כך אין HTML ב-JSON, ואין שני מימושים שיכולים להיפרד.
הנחיות לסוכני AI
אל תסתמכו על האמוג’י כדי לזהות שפה. השדה הקובע הוא
language(אוprogramming_languageבמסד). ה-API מחזיר אותו לצדicon.get_language_iconעדיין קיימת ומחזירה אמוג’י. היא נשארה לתאימות ולשימוש כ-fallback, ואינה מה שמוצג למשתמש ברשימות הקבצים.כשמוסיפים מקום תצוגה חדש, השתמשו ב-
lang_iconעם גודל מ-LANG_ICON_SIZESולא במספר קשיח, ובצד הלקוח ב-langIconElולא ב-innerHTML.
בדיקות
tests/test_lang_icons.py — הבדיקות המרכזיות:
כל שפה ברשימה קיימת כ-
<symbol>בספרייט, וכל<symbol>רשום ברשימה.הספרייט מסונכרן עם קבצי המקור (מריצה את הסקריפט ב-
--check).השרת והלקוח מייצרים HTML זהה — הבדיקה מריצה את קוד ה-JS ב-Node ומשווה מול
lang_iconעל 25 קלטים, רובם מקרי קצה של גודל.שני מסלולי הטיימליין מעבירים
icon_langושולפים את הגודל מאותו מקום.אין גדלים קשיחים בקריאות
lang_iconבתבניות.