ALWAYS respond in Hebrew! This includes:
- All answers and explanations
- Your thinking process
- Error messages and debugging info
- Only code/comments can be in English when appropriate
- Plan - Create detailed action plan before execution
- Execute - Step by step until completion
- Validate - Run
flutter analyzeafter EVERY change - Fix ALL errors before proceeding to next step
- Never skip validation - errors compound quickly!
Primary rule: Investigate first, ask only if you truly must.
Before writing any fix, perform the following steps on your own without asking the user:
- Understand the symptom - What did the user report? If critical details are missing that are needed to execute the fix (not to analyze) - ask everything in a single message.
- Investigate git - Run
git log --oneline -20and check commits that touched relevant code. - Read the code - Read the code before proposing any fix. Don't assume, know.
- Identify the root cause - If found, explain to the user what caused the bug before fixing it.
User reports bug
│
▼
Investigate first:
git log + read code
│
▼
Root cause found?
┌───┴───┐
YES NO
│ │
▼ ▼
Apply MINIMAL Ask user ONE message
fix & explain with ALL missing info
then investigate again
Bug fix ≠ adding code!
- FIRST - try to remove or revert code that caused the bug
- SECOND - try to change existing logic minimally
- LAST RESORT - add new code, only if truly necessary
- Adding more code to work around a bug = introducing future bugs
If you find yourself about to:
- Add a
try/catchto silence an error → find out why the error occurs first - Add a null check that "shouldn't be needed" → find out why it's null
- Add a workaround flag/boolean → reconsider the root cause
- Write more than ~15 lines to fix a single bug → something is wrong, reassess
- BLoC Pattern - State management (every feature needs: bloc/event/state)
- Repository Pattern - Separates data access from business logic
- Provider - For dependency injection across the app
lib/feature_name/
├── bloc/
│ ├── feature_bloc.dart # Business logic
│ ├── feature_event.dart # User actions/events
│ └── feature_state.dart # UI states
├── models/
│ └── feature_model.dart # Data models
├── repository/
│ └── feature_repository.dart # Data layer
└── view/
├── feature_screen.dart # Main screen
└── widgets/ # Feature-specific widgets
lib/
├── data/repository/
│ └── books_repository.dart # Central books management
├── models/
│ ├── books.dart # Book model (title, path, etc)
│ └── app_model.dart # Main app state
├── widgets/
│ ├── rtl_text_field.dart # RTL text input (USE THIS!)
│ └── [other shared widgets]
├── core/
│ └── scaffold_messenger.dart # UiSnack for messages
├── search/
│ ├── bloc/ # Search state management
│ └── search_repository.dart # Search engine
├── settings/
│ ├── settings_repository.dart # App settings
│ └── bloc/
├── bookmarks/repository/ # Bookmarks system
├── history/ # Reading history
├── personal_notes/ # User notes feature
├── pdf_book/ # PDF viewer screens
├── text_book/ # Text viewer screens
└── utils/
└── open_book.dart # Book opening logic
import 'package:otzaria_icons/otzaria_icons.dart';
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:otzaria/widgets/misc/rtl_icon.dart';
// First choice — the app's own set. ALWAYS plain Icon(), never RtlIcon:
Icon(OtzariaIcons.book_pdf_24_regular)
Icon(OtzariaIcons.otzaria_icon_2_page_24_regular)
Icon(OtzariaIcons.calendar_24_regular)
// Fallback — Fluent, only for what otzaria_icons does not have:
Icon(FluentIcons.settings_24_regular)
RtlIcon(FluentIcons.chevron_right_24_regular) // in _fluentMirrorMap
RtlIcon(FluentIcons.arrow_left_24_regular) // in _fluentMirrorMap — auto-mirrors to arrow_right in RTLWhat otzaria_icons is for. It is not a general-purpose set and does not try to cover Fluent. It exists for exactly four cases:
- An icon that breaks when mirrored for RTL — a geometric flip mangles it.
- An icon that is always shown in an RTL context, so it should simply be drawn that way.
- An icon Fluent does not have (
stander,torah_scroll,yoma_deilula, thealef_*/beit_*/tet_*families). - An icon Fluent has but whose form is less suitable for a seforim library (
book_pdfover a generic document).
Anything outside those four — generic UI chrome like dismiss, delete, copy, folder, settings, add, edit — stays on Fluent. Redrawing chrome buys nothing and costs consistency.
Which library — in this order:
Does otzaria_icons have an icon for it? |
Use |
|---|---|
| Yes | Icon(OtzariaIcons....) — always plain Icon |
| No | fluentui_system_icons, per the RtlIcon rule below |
| Neither, but Material does | Draw it in otzaria_icons — never import Material |
otzaria_icons is purpose-built for a Hebrew seforim library, so prefer it even when Fluent has something close: book_pdf for a PDF book rather than a generic document, search_in_the_book / search_in_the_library / search_in_the_settings for scoped search, the alef_* / beit_* / tet_* families for nikud, punctuation and font settings, stander / torah_scroll / yoma_deilula where nothing in Fluent applies.
Deliberate exceptions — these stay on Fluent:
| Case | Icon | Why |
|---|---|---|
| The seforim library itself | FluentIcons.library_24_regular/filled |
The Fluent library is the app's established symbol for it |
In-book search action (toolbar button, side-panel tab, context-menu חיפוש) in text_book/ and pdf_book/ |
FluentIcons.search_24_regular/filled |
Recognized as the search affordance in the reading screen |
Search icon inside a labeled button (ActionButton, FilledButton.icon — e.g. פתח חיפוש טקסט, חפש) |
FluentIcons.search_24_regular |
Beside a label the Fluent glyph reads cleaner. Icon-only buttons and field prefixes are not affected |
A direct link to a book or a section (קישור ישיר, deep links, inserting a hyperlink) |
FluentIcons.link_24_regular |
Distinct from links between books |
| The private-book badge on a library card (8px) | FluentIcons.person_24_regular |
The Otzaria person is drawn for legible sizes; at 8px it turns to mush. OtzariaIcons.person_24_regular stays everywhere it renders at 16px+ |
Links between books — the קישורים panel tab, the קישורים context-menu entry, דורות וקישורים — use OtzariaIcons.link_24_regular. The rule in practice: singular קישור / קישור ישיר is Fluent, plural קישורים is Otzaria. Two similar icons for the two meanings would be confusing, so keep the split.
Scoped search — pick the icon that names what is being searched. A generic magnifier says nothing; these do. None of the scoped icons has a filled twin, so a selected/unselected pair reuses the same glyph and lets color carry the state.
| Where | Icon |
|---|---|
Library screen search, סינון מפרשים, חפש בתוך המפרשים המוצגים, שמור וזכור search |
search_in_the_library_24_regular |
Notes search, calendar חפש גם בתיאור |
search_in_the_document_24_regular |
Calendar חפש רק בכותרת |
search_in_the_text_24_regular |
הוסף ספרים למעקב dialog |
search_in_the_book_24_regular |
| Settings search | search_in_the_settings_24_regular |
איתור כותרת boxes (TOC, alt-TOC), bookmarks search, חפש בתוך הקישורים המוצגים |
search_in_titles_24_regular |
| Gematria search | search_in_numbered_list_24_regular |
The navigation rail's חיפוש item is the exception: it keeps plain OtzariaIcons.search_24_regular / search_24_filled, because it is the app-wide search entry point and not scoped to anything.
Three shared widgets take an icon: / searchIcon: parameter for exactly this — OtzariaSearchField, ItemsListView, and any field's prefixIcon. Pass the scoped icon rather than wrapping the prefix in a leading: widget, which would drop OtzariaSearchField's focus-color and sizing behaviour.
Finding an icon: 135 icons, listed in OtzariaIcons.values and in the package's index.html catalog. The names follow the Fluent convention (<name>_24_<regular|filled>), so a Fluent name is usually the right thing to look up first. otzaria_icons is pinned by commit in pubspec.lock, and pubspec.lock is gitignored — if an icon in the catalog is undefined in your checkout, run flutter pub upgrade otzaria_icons.
Sizes: every icon is drawn on a 24px grid, and the _24_ in the name is the grid, not a size limit — pass size: freely. But the book_open_* family is drawn at different weights for different display sizes; otzaria_icon_2_page_24_regular/filled is the general-purpose "open book" and the only one of them with a filled twin, so it is what a nav rail or any regular/filled pair needs.
OtzariaIcons NEVER goes through RtlIcon. Every icon in the set is drawn right-to-left already — RtlIcon would flip an icon that already faces the correct way. test/widgets/rtl_icon_registered_usage_test.dart fails the build on any RtlIcon(OtzariaIcons....).
When to use RtlIcon vs Icon:
| Icon | Use |
|---|---|
Any OtzariaIcons.… |
Icon(...) — always |
Fluent icon registered in rtl_icon.dart (_fluentMirrorMap, _flippableIcons) |
RtlIcon(...) |
| Any other Fluent icon | Icon(...) — plain, no wrapper |
Icons currently registered in lib/widgets/misc/rtl_icon.dart:
_fluentMirrorMap (swaps to opposite-direction variant in RTL):
chevron_right/left_24/20/16_regulararrow_right/left_24_regular,arrow_right/left_24_filledarrow_previous/next_24_regularcalendar_24_regular/filled→calendar_rtl_*panel_left/right_24_regular,panel_left/right_24_filledtext_align_right/left_24_regular
_flippableIcons (geometrically flipped in RTL — no opposite-direction variant in Fluent):
book_24_regular,book_24_filledbook_information_24_regulartext_align_distributed_24_regularlist_24_regularcalendar_week_start_24_regular/filled,calendar_month_24_regular/filled
_flippableIcons is a stopgap, not a destination. A geometric flip mirrors the whole glyph, including asymmetric detail that was never meant to mirror. When an icon looks wrong flipped, the fix is to draw it right-to-left in otzaria_icons and drop it from this set — that is what book_star_24_regular did. The remaining six are the open candidates.
There is no _materialMirrorMap any more: lib/ contains zero Material icons, and nothing can feed one to RtlIcon (plugins declare icons by Fluent name through fluentIconFromName), so the map was dead code.
Some Fluent entries here have no call site left in lib/ — the app moved to the OtzariaIcons equivalent. Do not delete those: a plugin can still name a Fluent icon through fluentIconFromName in lib/plugins/utils/fluent_icon_resolver.dart, and it reaches RtlIcon as a variable.
If you need to flip a Fluent icon that is NOT yet registered:
Prefer drawing it in otzaria_icons — that is exactly what the package is for. Registering it in _flippableIcons is the fallback when you cannot. Either way, do NOT add manual Transform.flip/Transform.scale in feature files.
Never use:
- Material Icons — no exceptions.
lib/is Material-free; if Material has something Fluent lacks, draw it inotzaria_icons - Cupertino Icons
- Any icon font other than
otzaria_iconsandfluentui_system_icons RtlIconwith anOtzariaIconsicon — it is already RTLmirrorIconparameter on any widget — FORBIDDEN, removed in commit 3b4d357- Manual
Transform.scale(scaleX: -1, ...)orTransform.flip(flipX: true, ...)around icons — register inrtl_icon.dartinstead. On anOtzariaIconsicon this silently points it the wrong way, and neither the analyzer norrtl_icon_registered_usage_test.dartcatches it — when replacing a Fluent icon, check the call site for a hand-rolled flip first - Comments explaining why
RtlIconorIcon(...)was chosen — the decision rule is documented here; do NOT repeat it inline in code - Editing
lib/plugins/utils/fluent_icon_resolver.dartto point atotzaria_icons— it is the generated Fluent-name contract for plugins
import 'package:otzaria/core/ui_snack.dart';
import 'package:otzaria/core/messages/messages_exports.dart';
UiSnack.show(CommonMessages.savedSuccessfully);
UiSnack.showError(ReportMessages.sendFailed);
UiSnack.show(UiSnack.textCopied); // Legacy alias → CommonMessagesMessage texts are centralized (MANDATORY): Never pass a hardcoded string literal to UiSnack. Every message lives in lib/core/messages/ — one catalog per domain (CommonMessages, ReportMessages, SettingsMessages, TextBookMessages, ToolsMessages, NotesMessages, LibraryMessages, PluginMessages, PdfMessages). Fixed texts are static const; parameterized texts are static functions. Add new messages to the matching catalog (or CommonMessages if shared).
Never use:
- Hardcoded message strings at
UiSnackcall sites — add tolib/core/messages/instead ScaffoldMessenger.of(context).showSnackBar()- Custom snackbar widgets
- Toast packages
- Alert dialogs for simple messages
import 'package:otzaria/widgets/rtl_text_field.dart';
RtlTextField(
controller: _controller,
decoration: InputDecoration(labelText: 'חיפוש'),
onSubmitted: (value) => _handleSearch(),
autofocus: true,
)NEVER use regular TextField - it breaks RTL support!
import 'package:otzaria/widgets/widgets_exports.dart';
// Single button dialog (confirm only)
showSingleActionDialog(
context: context,
title: 'כותרת',
content: 'תוכן הדיאלוג',
confirmText: 'אישור',
);
// Two-button dialog (cancel + confirm)
showTwoActionsDialog(
context: context,
title: 'כותרת',
content: 'תוכן הדיאלוג',
cancelText: 'ביטול',
confirmText: 'אישור',
);
// Warning dialog (user should ideally cancel)
showWarningDialog(
context: context,
title: 'אזהרה',
content: 'פעולה זו היא סופית',
subtitle: 'שים לב שלא ניתן לבטל פעולה זו', // red text
cancelText: 'ביטול',
confirmText: 'המשך',
);Dialog Styling Rules (CRITICAL):
- SingleActionDialog: single button - FilledButton (primary/onPrimary)
- TwoActionsDialog:
- Cancel = FilledButton.tonal (surfaceContainerHighest/onSurface)
- Confirm = FilledButton (primary/onPrimary)
- WarningDialog:
- Cancel = FilledButton (primary/onPrimary) - recommended (safe choice)
- Confirm = TextButton (transparent background, error text color) - dangerous
- Subtitle = error color (red)
Never use:
showDialogwith customAlertDialogdirectly- Material
SimpleDialog - Custom dialog widgets without the standard styling
- Hardcoded colors (Colors.red, Colors.blue, etc.)
import 'package:otzaria/widgets/widgets_exports.dart';
// Recommended action button (Primary style)
ActionButton.recommended(
text: 'שנה מיקום',
onPressed: () => _changeLocation(),
isLoading: false, // optional - shows loading indicator
);
// Neutral/non-recommended action button (Tonal style)
ActionButton.neutral(
text: 'איפוס',
onPressed: () => _resetSettings(),
isLoading: false, // optional - shows loading indicator
);
// Ghost (transparent, neutral)
ActionButton.ghost(
text: 'ביטול',
onPressed: () => _cancel(),
);
// Warning (transparent background, error-color text — for destructive actions)
ActionButton.warning(
text: 'מחק לצמיתות',
onPressed: () => _delete(),
);Button Styling Rules (CRITICAL):
- ActionButton.recommended: FilledButton (primary background, onPrimary text)
- ActionButton.neutral: FilledButton.tonal (surfaceContainerHighest background, onSurface text)
- ActionButton.ghost: TextButton (transparent, neutral color)
- ActionButton.warning: TextButton (transparent, cs.error text — for destructive confirmations)
- NEVER use hardcoded colors - always use
Theme.of(context).colorScheme
When to use which button:
ActionButton.recommended- recommended actions (change settings, choose location, update, add)ActionButton.neutral- neutral or dangerous actions (reset, delete, remove, stop)ActionButton.ghost- secondary inline text actions (cancel, close, skip)ActionButton.warning- destructive confirmation (delete, clear, overwrite — matches WarningDialog's confirm button)
Never use:
ElevatedButton,TextButton,OutlinedButtondirectly- Custom button widgets without the standard styling
- Material
IconButtonfor primary actions - Hardcoded colors
import 'package:otzaria/settings/settings_card.dart';
SettingsCard(
title: 'כותרת הקטגוריה',
subtitle: 'תיאור אופציונלי', // אופציונלי
children: [
ListTile(...),
// Divider is added automatically between items
SwitchListTile(...),
],
);Card Styling Rules:
- Title: titleMedium, bold, primary color
- Subtitle: bodySmall, onSurfaceVariant color (optional)
- Card: surface color, rounded corners (20), subtle border
- Dividers: Automatic between children, surfaceContainerHighest color, thickness 1.5
Hover Effects:
- Remove hover from ListTile rows containing action buttons:
hoverColor: Colors.transparent - Hover should ONLY appear on the action buttons themselves
- This prevents double-hover effect and improves UX
NEVER add the following anywhere outside lib/theme/:
hoverColoronInkWell/ListTile/ any widget (exceptColors.transparenton ListTile with action buttons)splashColoron any widgetoverlayColoron any widget.withValues(alpha: ...)— color transparency overrides
Why: These were used to work around a dark-mode color bug (fixed in commit f938a1860 via ColorScheme.fromSeed). Now the theme computes all interaction colors correctly. Adding them manually breaks theme consistency and will break again when themes change.
If you need to define a custom interaction color or transparency:
→ Define it in lib/theme/app_theme_data.dart or lib/theme/app_surfaces.dart, not in feature files.
Exceptions (the only allowed uses outside lib/theme/):
hoverColor: Colors.transparenton aListTilethat contains action buttons in its trailing/leading (prevents double-hover)BoxShadowcolors with.withValues(alpha: ...)— shadows require transparency by nature- Loading overlays / semi-transparent backgrounds that are structural (not interaction feedback)
import 'package:otzaria/widgets/widgets_exports.dart';
// Setting with 2-4 options
SegmentedSettingsTile<String>(
icon: FluentIcons.text_font_info_24_regular,
title: 'הצגת הניקוד',
subtitle: 'הניקוד יוצג בכל הספרים',
options: const [
SegmentOption(value: 'always', label: 'הצג תמיד'),
SegmentOption(value: 'tanach_only', label: 'הצג בתנ"ך'),
SegmentOption(value: 'never', label: 'אל תציג'),
],
currentValue: nikudDisplayMode,
onChanged: (value) {
// update BLoC
},
);When to use SegmentedSettingsTile:
- Settings with 2-4 mutually exclusive options
- When the user needs to pick exactly one value from a small set
- Modern alternative to a RadioButton group or multiple SwitchListTiles
Styling:
- Selected: primary color with 20% opacity background
- Unselected: card color background
- Rounded corners (8)
- Fits in single row within SettingsCard
Title can be:
- String - plain text
- Widget - for advanced styling (e.g. RichText with mixed colors)
Never use:
- RadioButton groups for 2-4 options
- Multiple SwitchListTile for mutually exclusive options
- Custom segmented button implementations
The settings screen has an English mode (lib/settings/l10n/). Every new user-visible string under lib/settings/ must be wrapped — an unwrapped string silently stays Hebrew when the user picks English.
import 'package:otzaria/settings/l10n/settings_l10n_exports.dart';
Text(context.settingsText('גודל גופן הספר'))
// Placeholders — never string interpolation, so the translation can reorder them:
context.settingsText('יש כרגע {count} דיווחים שמורים בתור', args: {'count': pendingCount})
// Identical Hebrew with different translations — separate with a context:
context.settingsText('ספריה', context: 'titleBar') // → "Library"
context.settingsText('ספריה') // → "Seforim Library"The Hebrew source string IS the translation key. Never invent a key like 'settings.font.size': a maintainer looking for a screen greps the Hebrew text they see on it, and that has to keep working. The English text lives only in lib/settings/l10n/settings_en.arb.
After adding or changing a string, regenerate the catalog:
dart run tool/generate_settings_l10n.dartIt reads the ARB and writes the const map in lib/settings/l10n/settings_catalogs.g.dart. It also runs on flutter run / flutter build / flutter test via the build hook — but not on hot reload, so an ARB edit needs a restart to appear.
The generator validates the ARB itself (duplicate keys, placeholder mismatch). What catches a missing translation is test/settings/l10n/settings_l10n_test.dart, which scans the code for settingsText calls and fails on any key absent from the ARB — plus the reverse, an ARB entry no longer used. Run it after touching any settings string:
flutter test test/settings/l10n/Never do:
- A bare string literal on a settings widget's
title/subtitle/label/tooltip/ dialog text - String interpolation inside the key (
'שמור ${count} ספרים') — useargs:instead - A non-Hebrew invented key
- Editing
settings_catalogs.g.dartby hand — it is generated, and your edit is lost on the next build textDirectionorDirectionalityto "fix" the English mode — the app stays RTL; only the settings screen switches locally
Two traps that make a string render Hebrew even though it looks wrapped:
-
A string reaching
settingsTextthrough a variable is invisible to the scanner. It reads literal arguments only, socontext.settingsText(item.label)passes the coverage test with no translation existing. When the text arrives via a variable, field, or table, add an explicit case totest/settings/l10n/settings_variable_labels_test.dart. -
A dialog builds in the Navigator's Overlay, outside the settings widget tree, so it inherits neither the language nor the direction. Open it through
settingsDialogBuilder:showDialog(context: context, builder: settingsDialogBuilder(context, (_) => const MyDialog()));
Strings outside lib/settings/ are Hebrew-only by design — do not wrap them. Two areas are the exception and do go through the same catalog:
lib/navigation/— the fixed navigation rail and the title-bar screen names, because the settings screen is reached from them.lib/tour/— the guided tour and the live tips. Every new tour step title/body and every live-tip title/description needs an ARB entry, same as a settings string; seedocs/guided_tour_developer_guide.md. Two rules specific to the tour: a step'sbodymust stay a plain string literal (a variable value goes in as a placeholder — a keyboard shortcut viashortcut:filling{shortcut}), and coverage is guarded bytest/settings/l10n/settings_variable_labels_test.dart, which builds the steps for real, so a step with no translation fails there rather than rendering Hebrew.
The app uses locale: Locale("he", "IL") + GlobalWidgetsLocalizations.delegate in MaterialApp.
This sets Directionality.rtl globally for the entire widget tree — every Text inherits RTL automatically.
textDirection rule — Critical:
- NEVER add
textDirection: TextDirection.rtltoText— it is completely redundant. - ADD
textDirection: TextDirection.ltronly for inherently LTR content:- OS file / folder paths
- Email addresses
- Version numbers / hash values
- URLs
- Technical identifiers (clearly LTR format)
- For parameters like
subtitleDirection— passtextDirectiontoTextonly when the value is LTR:// Correct: textDirection: subtitleDirection == TextDirection.ltr ? TextDirection.ltr : null, // Wrong — never pass TextDirection.rtl: // textDirection: subtitleDirection, // ❌ when the default is rtl
- Use
RtlTextFieldfor all text inputs - Test UI with Hebrew text before committing
// 1. Events - User actions
sealed class FeatureEvent extends Equatable {
const FeatureEvent();
}
class LoadDataEvent extends FeatureEvent {
const LoadDataEvent();
@override
List<Object> get props => [];
}
// 2. States - UI states
sealed class FeatureState extends Equatable {
const FeatureState();
}
class InitialState extends FeatureState {
@override
List<Object> get props => [];
}
class LoadingState extends FeatureState {
@override
List<Object> get props => [];
}
class LoadedState extends FeatureState {
final Data data;
const LoadedState(this.data);
@override
List<Object> get props => [data];
}
// 3. Bloc - Logic
class FeatureBloc extends Bloc<FeatureEvent, FeatureState> {
final FeatureRepository repository;
FeatureBloc({required this.repository}) : super(InitialState()) {
on<LoadDataEvent>(_onLoadData);
}
Future<void> _onLoadData(
LoadDataEvent event,
Emitter<FeatureState> emit,
) async {
emit(LoadingState());
try {
final data = await repository.fetchData();
emit(LoadedState(data));
} catch (e) {
emit(ErrorState(e.toString()));
UiSnack.showError('שגיאה: ${e.toString()}');
}
}
}class FeatureRepository {
final DataSource dataSource; // Could be API, DB, file system
FeatureRepository({required this.dataSource});
Future<List<Item>> getItems() async {
try {
final rawData = await dataSource.fetch();
return rawData.map((e) => Item.fromJson(e)).toList();
} catch (e) {
throw RepositoryException('Failed to get items: $e');
}
}
}try {
await riskyOperation();
} catch (e, stackTrace) {
// Log for debugging
debugPrint('Error: $e\n$stackTrace');
// Show user-friendly message
UiSnack.showError('אירעה שגיאה: ${e.toString()}');
// Update state if needed
emit(ErrorState(e.toString()));
}/// Returns a list of books by category
///
/// [category] - the category name
/// Returns [Future<List<Book>>] - list of books or error
/// Throws [RepositoryException] if data not found
Future<List<Book>> getBooksByCategory(String category) async {
// Implementation
}הכלל: פחות הערות, וקצרות. הוסף הערה רק כשהיא באמת נצרכת.
- כמות - אל תוסיף הרבה הערות. רוב הקוד צריך להסביר את עצמו דרך שמות ברורים.
- אורך - הערה נצרכת תהיה קצרה - מקסימום 2 שורות.
- קהל היעד - כתוב הערה רק למי שקורא את הקוד בפעם הראשונה. ההערה מסבירה למה הקוד עושה משהו לא מובן מאליו, או מתעדת מלכוד שאם ישנו אותו יחזור באג. זו ההצדקה היחידה להערה.
- לא רלוונטי - אסור להערות שמתעדות היסטוריה: "פעם היה כך", "שונה ב-commit X", "הוספנו כי...", "TODO ישן", קוד מבוטל בהערה. למשתמש שקורא עכשיו לא מעניין מה היה - הגיט מתעד את זה.
// ❌ רע - מתעד היסטוריה, לא רלוונטי לקורא:
// פעם השתמשנו ב-setFullScreen אבל זה איבד WS_VISIBLE אז שינינו
// ✅ טוב - מזהיר ממלכוד שיחזיר באג אם ישונה (קצר):
// setFullScreen על חלון מוסתר מאבד WS_VISIBLE - חובה להציג קודםאם נתקלת בהערה קיימת שמפרה את ההנחיה משמעותית (ארוכה מדי, מתעדת היסטוריה, מיותרת) - תקן/מחק אותה כחלק מהעבודה על אותו קובץ.
flutter analyze # Must pass with ZERO errors/warnings
flutter test test/feature/ # Run ONLY tests related to your changes
dart format lib/file.dart # Format ONLY files you modifiedTip: The project uses
dart_pre_commitas a git pre-commit hook. After cloning, run once:dart run tool/install_git_hooks.dart. From that point,dart formatanddart analyzerun automatically on staged files before every commit. Tests must still be run manually — they are not part of the hook.
| Change Type | Tests to Run |
|---|---|
Modified lib/search/ |
flutter test test/search/ |
| Modified shared widget | All tests using that widget |
| New feature | All tests for that feature |
| Changed interface/contract | All affected integration tests |
| Modified core logic | Full test suite |
Text Book Viewer
| Area | Test File |
|---|---|
| Screen actions (overflow, layout) | test/text_book/view/text_book_screen_actions_test.dart |
| שימור חלונית הניווט במעבר טאב | test/text_book/view/text_book_nav_panel_preserved_test.dart |
| יעד סיור לחלונית הניווט (מפתח יציב) | test/text_book/view/widgets/nav_panel_tour_target_test.dart |
| Search controller sync | test/text_book/text_book_search_query_sync_test.dart |
| Search screen | test/text_book/view/text_book_search_screen_test.dart |
| מסלול המנוע בחלונית החיפוש בספר (מרווח, זיהוי הספר) | test/text_book/view/text_book_search_engine_route_test.dart |
| קאש שורות הספר לחיפוש (שחרור בטאב רקע) | test/text_book/view/text_book_search_content_cache_test.dart |
| TOC navigator UI | test/text_book/view/toc_navigator_screen_test.dart |
| TOC navigator internals | test/text_book/view/toc_navigator_internals_test.dart |
| Combined view helpers (shouldShow…) | test/text_book/view/combined_view/combined_book_screen_test.dart |
| TabbedCommentaryPanel tab switching / onTabChanged | test/text_book/view/tabbed_commentary_panel_test.dart |
| Page shape commentary selection | test/text_book/view/page_shape_commentary_selection_test.dart |
| חלונית הצד של צורת הדף (3 לשוניות) | test/text_book/view/page_shape/page_shape_sidebar_tabs_test.dart |
| תפריט הקשר בצורת הדף (מפרשים / קטע היעד) | test/text_book/view/page_shape/simple_text_viewer_context_menu_test.dart |
| תת-תפריט "מפרשים" המשותף + מדיניות הצגה | test/text_book/utils/commentators_context_menu_test.dart |
| SimpleTextViewer | test/text_book/view/page_shape/simple_text_viewer_test.dart |
| Selected text copy/restore | test/text_book/view/selection/selected_text_copy_test.dart, …selected_text_restore_test.dart |
| SelectionSyncController | test/text_book/view/selection/selection_sync_controller_test.dart |
| Commentary open-filter request | test/text_book/view/commentary_list_base_open_filter_test.dart |
| Commentary search focus | test/text_book/view/commentary_search_focus_test.dart |
| Commentary grouping | test/text_book/commentary_grouping_test.dart |
| Book source dialog | test/text_book/view/book_source_dialog_test.dart |
| Error report dialog | test/text_book/view/error_report_dialog_test.dart |
Text Book BLoC
| Area | Test File |
|---|---|
| BLoC state equality | test/text_book/bloc/text_book_state_test.dart |
| Background content loading | test/text_book/bloc/background_full_content_loading_test.dart |
| Continuous reading mode | test/text_book/bloc/continuous_reading_mode_test.dart |
| Selected link types persistence | test/text_book/bloc/selected_link_types_persistence_test.dart |
| visibleIndices throttling (scroll perf) | test/text_book/bloc/visible_indices_throttle_test.dart |
Data / Database
| Area | Test File |
|---|---|
| DatabaseLibraryProvider (links, alt-toc, isolate regressions) | test/data_providers/database_library_provider_test.dart |
| DatabaseLibraryProvider has-book | test/data_providers/database_library_provider_has_book_test.dart |
| UserBooksDB | test/data_providers/user_books_database_holder_test.dart |
| FileSystemLibraryProvider | test/data_providers/file_system_library_provider_test.dart |
| ExternalCatalogMapper | test/data_providers/external_catalog_mapper_test.dart |
| TantivyDataProvider (search index) | test/data/data_providers/tantivy_data_provider_test.dart |
| External books scanner | test/data/data_providers/scan_external_books_test.dart |
| Library book search (fuzzy + acronyms) | test/data/repository/book_search_fuzzy_match_test.dart |
| אינדקס הביגרמים של הכינויים (איתור מקורות — קבוצת-על) | test/data/cache/acronyms_bigram_index_test.dart |
| פתרון ספר של מפרש בדיאלוג "איתור מקורות" (id → כותרת, אינדקס העץ) | test/find_ref/find_ref_book_by_id_test.dart |
Search
| Area | Test File |
|---|---|
| Find-match utils | test/search/find_match_utils_test.dart |
| Catalogue order helper | test/search/search_catalogue_order_helper_test.dart |
| Enhanced search field | test/search/enhanced_search_field_test.dart |
| Book facet | test/search/book_facet_test.dart |
| Facet helper | test/search/facet_helper_test.dart |
| Search BLoC facet counts | test/search/search_bloc_facet_counts_test.dart |
| Search scope preferences | test/search/search_scope_preferences_test.dart |
| עץ ניווט תוצאות (רשימת סינון, גלוּת הבחירה, פתיחת ענפים) | test/search/search_navigation_tree_test.dart |
| חלונית סינון התוצאות מקצה לקצה (שדה "איתור ספר") | test/search/search_facet_filtering_book_filter_test.dart |
| ניתוב חיפוש-בספר: פשוט מול מנוע | test/search/utils/in_book_search_routing_test.dart |
| מדיניות ההתאמה (טווח קרבה + התאמת מילים) | test/search/search_match_policy_test.dart |
| פתיחת תוצאה: העברת הקונפיגורציה לטאב הקריאה | test/search/tantivy_search_results_in_book_routing_test.dart |
| שקילות מנוע ↔ הדגשה במרווח בין מילים | test/search/highlight_engine_distance_parity_test.dart |
| הדגשה במדיניות התאמה — רק בשורות שהמנוע החזיר | test/utils/highlight_match_policy_test.dart |
| שימור קונפיגורציית החיפוש בשכפול/שחזור טאב ובשמירה ל-JSON | test/tabs/models/tab_search_state_clone_test.dart |
| Gematria search | test/tools/gematria/gematria_search_test.dart |
Personal Notes
| Area | Test File |
|---|---|
| Notes screen | test/personal_notes/personal_notes_screen_test.dart |
| Note tile | test/personal_notes/widgets/note_tile_test.dart |
| Note editor | test/personal_notes/personal_note_editor_test.dart |
| Note draft service | test/personal_notes/personal_note_draft_service_test.dart |
| Note content view | test/personal_notes/personal_note_content_view_test.dart |
| Notes export | test/personal_notes/personal_notes_export_test.dart |
| סינון "הצג רק הערות לטקסט הנראה" (BLoC) | test/personal_notes/bloc/personal_notes_visible_filter_test.dart |
| שורות גלויות בחלונית ההערות (הרכבה, גלילה, PDF) | test/personal_notes/widgets/personal_notes_sidebar_visible_lines_test.dart |
Settings
| Area | Test File |
|---|---|
| Nikud display service | test/settings/nikud_display_service_test.dart |
| Settings repository | test/settings/settings_repository_test.dart |
| Settings screen controller | test/settings/settings_screen_controller_test.dart |
| Bookmark model | test/settings/history/bookmark_model_test.dart |
| Custom folders BLoC | test/settings/services/custom_folders/custom_folders_bloc_test.dart |
| Backup service (roundtrip, plugins, auto-backup) | test/settings/services/backup_service_test.dart |
| Backup store (blobs, dedup, GC) + maintenance helpers | test/unit/settings/backup/backup_store_test.dart |
| Backup rotation (GFS) | test/unit/settings/backup/backup_rotation_test.dart |
| Backup archive merge rules | test/unit/settings/backup/backup_merge_test.dart |
| SegmentedSettingsTile | test/settings/widgets/segmented_settings_tile_test.dart |
| SwitchSettingsTile | test/settings/widgets/switch_settings_tile_test.dart |
Widgets (shared)
| Area | Test File |
|---|---|
| App menu | test/widgets/app_menu_test.dart |
| App top bar | test/widgets/app_top_bar_test.dart |
| Context overlay panel | test/widgets/context_overlay_panel_test.dart |
| Context menu (incl. hover preview + pinning) | test/widgets/app_context_menu_test.dart |
| Link preview panel (placement, pin, scroll anchor) | test/widgets/link_preview_overlay_test.dart |
| Dual adaptive reader pane | test/widgets/dual_adaptive_reader_pane_test.dart |
| Nav rail item | test/widgets/nav_rail_item_test.dart |
| Reader side panel shell | test/widgets/reader_side_panel_shell_test.dart |
| Responsive action bar | test/widgets/responsive_action_bar_test.dart |
| Scrollable list scrollbar | test/widgets/scrollable_positioned_list_scrollbar_test.dart |
| Smooth mouse-wheel scrolling | test/widgets/smooth_wheel_scroll_test.dart |
| Smart text render settings | test/widgets/smart_text/render_settings_test.dart |
| Smart text ↔ plugin section sync gate | test/widgets/smart_text/smart_text_section_sync_gate_test.dart |
| Work/indexing status overlays | test/widgets/work_status_overlay_test.dart, …indexing_status_overlay_test.dart |
| App dropdown/search menu | test/widgets/app_dropdown_field_test.dart, …app_search_menu_test.dart |
| שימור כיוון הפותח בתפריטים מעוגנים (הגדרות באנגלית) | test/widgets/app_menu_direction_test.dart |
| כיוון כרטיסי הסיור/טיפים לפי שפת ההגדרות | test/tour/widgets/tour_cards_direction_test.dart |
| Search pane base | test/widgets/search_pane_base_test.dart |
נתוני פופאפ "אוצריא מתגייסת" (assets/support_organizations.json) |
test/services/support_organizations_test.dart |
| פופאפ "אוצריא מתגייסת" (תצוגה, שגיאת טעינה, פענוח לוגואים ומטמון) | test/widgets/dialogs/ad_popup_dialog_test.dart |
Navigation / Startup
| Area | Test File |
|---|---|
| Navigation BLoC | test/navigation/navigation_bloc_test.dart |
| Startup guard / auto-reindex | test/navigation/startup_work_gate_test.dart, …startup_auto_reindex_test.dart, …new_books_indexing_guard_test.dart |
Other Features
| Area | Test File |
|---|---|
| Bookmarks BLoC | test/bookmarks/bookmark_bloc_test.dart |
| Workspaces BLoC | test/workspaces/bloc/workspace_bloc_test.dart |
| מחוות החלקה בין טאבים (סינון התקנים, כיוון) | test/tabs/reading_screen_move_tab_state_test.dart, …tab_swipe_direction_test.dart |
| מעבר לטאב שנפתח כשמסך הקריאה מנותק (issue #877) | test/tabs/reading_screen_offscreen_tab_open_test.dart |
Windows installer scripts (.iss invariants) |
test/installer/installer_scripts_test.dart |
| App paths / install-mode detection | test/core/app_paths_test.dart |
| Library browser | test/library/view/library_browser_preview_width_test.dart, …grid_items_test.dart, …library_browser_flat_tree_test.dart |
| תאריך עברי + דף יומי בסרגל הספרייה (היום הלוחי) | test/library/view/library_daf_yomi_test.dart |
| Empty library screen | test/empty_library/empty_library_screen_test.dart |
| PDF isolate / rasterizer | test/printing/pdf_isolate_test.dart, …pdf_text_rasterizer_test.dart |
| PDF in-book search highlight pattern | test/pdf_book/pdf_search_highlight_pattern_test.dart |
| ניתוב החיפוש בתוך PDF (פשוט מול מנוע) | test/pdf_book/pdf_search_in_book_routing_test.dart |
| Printing models | test/printing/print_content_models_test.dart |
| File sync / background sync | test/migration/sync/file_sync_service_prune_test.dart, …background_db_sync_worker_test.dart, …background_sync_initializer_test.dart |
| DB migration / generator | test/migration/generator_create_and_process_book_test.dart, test/migration/dao/daos/database_locked_test.dart |
| Indexing repository | test/indexing/repository/indexing_repository_test.dart |
| External catalog | test/external_catalog/external_catalog_repository_test.dart, …settings_helper_test.dart |
| Plugins | test/plugins/utils/reader_location_resolver_test.dart, …plugin_store_link_parser_test.dart, …plugin_bridge_adapter_test.dart |
| Plugin highlights / reader section tracking | test/plugins/services/plugin_highlight_registry_test.dart, …reader_section_content_tracker_test.dart, …reader_section_sync_gate_test.dart |
| Plugin foreground suspend/resume | test/plugins/services/plugin_runtime_dispatcher_test.dart |
Tools & plugins as reading tabs
| Area | Test File |
|---|---|
| ToolTab model (JSON, clone, dedupe) | test/tabs/models/tool_tab_test.dart |
| Tool catalog + availability reasons | test/tools/tool_catalog_test.dart |
| Tools launcher panel (search, grouping, grid columns, tile layout) | test/tools/tools_launcher_panel_test.dart |
| Tool tab focus (WebView regression) | test/tools/tool_tab_focus_test.dart |
| Tool tab dedupe / focus-existing | test/tabs/bloc/tool_tab_dedupe_test.dart |
readingPane (plugin reader API context) |
test/tabs/reading_pane_test.dart |
| Shamor Zachor | test/shamor_zachor/shamor_zachor_test.dart (+ 4 more in that dir) |
| Dictionary lookup | test/tools/dictionary/dictionary_lookup_repository_test.dart |
| Laaz Rashi commentary line-lookup | test/tools/dictionary/laaz_rashi_line_lookup_test.dart |
| Laaz Rashi commentary sub-block widget | test/tools/dictionary/laaz_commentary_subblock_test.dart |
| Laaz Rashi commentary wiring (surfaces) | test/tools/dictionary/laaz_commentary_wiring_test.dart |
| Commentary reverse links | test/text_book/commentary_reverse_links_test.dart |
| Inline links | test/models/inline_links_test.dart |
| Dialog navigation | test/widgets/dialogs/dialog_navigation_test.dart |
| Focus restore | test/core/focus_restore_test.dart |
| Models (books, links) | test/models/books_test.dart, …links_test.dart, …phone_report_data_test.dart |
| Link types (נרמול, סוג קנוני, תוויות) | test/models/link_types_test.dart |
| Utils (page map builder, page converter, TOC parser) | test/utils/page_map_builder_test.dart, …page_converter_test.dart, …toc_parser_test.dart |
| זיהוי פורמט מסמך + registry הסיומות | test/utils/file/document_format_test.dart |
| עקביות ה-registry מול הצרכנים (FilePicker, סורק, מודל הספר) | test/utils/file/format_registry_consistency_test.dart |
| Golden regression של ממיר Word (17 תרחישים, שקילות DOCX/DOCM/DOTX/DOTM) | test/utils/file/docx_golden_test.dart (fixtures ב-docx_golden_fixtures.dart) |
| שרשרת מלאה לפורמטי OOXML (סריקה→המרה→TOC→אינדוקס) | test/utils/file/ooxml_formats_pipeline_test.dart |
| ממיר ODT | test/utils/file/odt_to_otzaria_test.dart |
| Parser RTF (state machine, דפי-קוד, עברית) | test/utils/file/rtf_to_otzaria_test.dart |
| קריאת ספר file-backed לפי פורמט | test/utils/file/read_file_backed_book_text_test.dart |
| מגבלות פריסת ZIP (zip bomb) | test/utils/file/zip_limits_test.dart |
| הקשחה מול קובץ פגום/קטוע/זדוני (כשל בקול, לא פלט חלקי) | test/utils/file/malformed_document_hardening_test.dart |
| קורא מכולת CFB/OLE2 (תשתית ל-DOC/WBK) | test/utils/file/cfb_reader_test.dart |
| ממיר Word בינארי ישן (FIB, piece table, ניתוב WBK) | test/utils/file/legacy_word_to_otzaria_test.dart |
| שכבת המאפיינים של Word הבינארי (sprm, וריאנט Bi, יישור) | test/utils/file/legacy_word_properties_test.dart |
| חילוץ תמונות מ-Word הבינארי (עץ OfficeArt, תקרות, קלט פגום) | test/utils/file/legacy_word_pictures_test.dart |
| ממיר Word שנשמר כ-XML (Flat OPC ו-WordML 2003) | test/utils/file/word_xml_to_otzaria_test.dart |
| צימוד פלט הממיר לגרסתו (מונע מטמון שמגיש פלט באגי) | test/utils/file/converter_versions_test.dart |
| עמידות סריקה לקובץ פגום (§76) | test/migration/generator_corrupted_file_test.dart |
| אינטגרציה: סריקת תיקייה לכל הפורמטים → DB → פתיחה → זיהוי שינוי | test/migration/sync/file_sync_document_formats_test.dart |
| מחולל קורפוס ה-fixtures (כל פורמט נפתח, כל מקרה-קצה נכשל נכון) | test/tool/document_fixtures_generator_test.dart |
| זיהוי קידוד טקסט — שרשרת הזיהוי, BOM, זנב קטוע, כפיית קידוד | test/utils/file/text_encoding_detection_test.dart |
| טבלאות המיפוי (Windows-1255, ISO-8859-8, CP862) מול התקנים | test/utils/file/text_encoding_tables_test.dart |
| קורפוס הזהב של הקידודים (40+ קבצים, טווחי confidence) | test/utils/file/text_encoding_corpus_test.dart (מחולל ב-tool/generate_text_encoding_fixtures.dart) |
| רגרסיה מול מפענח הקידודים הקודם (מה נשמר, מה השתנה בכוונה) | test/utils/file/text_encoding_regression_test.dart |
| תכונות הקידוד על קלט מוגרל (סבב שלם, שיבוש, חיתוך, דטרמיניזם) | test/utils/file/text_encoding_fuzz_test.dart |
| צנרת הקידודים מקצה לקצה (פתיחת ספר, בניית DB, אינדוקס) | test/utils/file/text_encoding_pipeline_test.dart |
| ייבוא ספרים בכל קידוד לתוך SQLite (סריקה→שורות→TOC) | test/migration/sync/file_sync_text_encodings_test.dart |
| ביצועי הזיהוי (חסימת דגימה, תפוקת batch, השוואה לקודם) | test/utils/file/text_encoding_performance_test.dart |
| קורפוס קידודים חיצוני אמיתי (מדלג כשאינו על המכונה) | test/utils/file/text_encoding_real_corpus_test.dart |
| Utils (link processing) | test/text_book/utils/link_processing_test.dart |
| גודל פענוח תמונות (cacheWidth על נכסים כבדים) | test/utils/ui/image_decode_size_test.dart |
| Hebrew text utils (migration) | test/migration/hebrew_text_utils_test.dart |
| Text book searcher (in-book search) | test/text_book/models/text_book_searcher_test.dart |
| Note text utils | test/personal_notes/note_text_utils_test.dart |
| Shortcut validator | test/shortcuts/shortcut_validator_test.dart |
| Core (activation queue/channel, error log) | test/core/ |
| Error logging | test/core/main_error_logging_test.dart, test/services/direct_error_report_service_test.dart |
Calendar (lib/tools/calendar/)
| Area | Test File |
|---|---|
| Cubit (אירועים, פלאגינים, התראות) | test/tools/calendar/utils/calendar_cubit_test.dart |
| סדר אירועים (לפי שעה) — cubit + משווים | test/tools/calendar/utils/calendar_event_sorting_test.dart |
| סדר אירועים בתצוגה (פאנל + תא היום) | test/tools/calendar/widgets/calendar_events_order_test.dart |
| זמני היום / אזורי זמן | test/tools/calendar/utils/calendar_daily_times_test.dart, …calendar_timezone_test.dart |
| כרטיסי זמנים (composite) ורישום הזמנים | test/tools/calendar/widgets/calendar_composite_entries_test.dart |
| עזרי זמנים / מולד | test/tools/calendar/helpers/zmanim_helpers_test.dart, …molad_helpers_test.dart |
| דיאלוגים | test/tools/calendar/dialogs/calendar_dialogs_test.dart |
| תא היום (JewishCalendar משותף) | test/tools/calendar/widgets/day_cell_shared_calendar_test.dart |
| פוקוס וניווט מקלדת | test/tools/calendar/widgets/calendar_widget_focus_test.dart, …calendar_top_bar_focus_test.dart |
| החלקה בין חודשים | test/tools/calendar/widgets/calendar_main_panel_swipe_test.dart |
| פריסה רספונסיבית | test/tools/calendar/calendar_screen_responsive_test.dart |
- Bloc: Use
bloc_testpackage - Repository: Mock dependencies with
mockito - Always add/update tests for code you change
- Example:
blocTest<SearchBloc, SearchState>(
'emits SearchLoaded when search succeeds',
build: () => SearchBloc(repository: mockRepository),
act: (bloc) => bloc.add(SearchRequested('query')),
expect: () => [SearchLoading(), SearchLoaded(results)],
);flutter pub get # Install dependencies
flutter pub outdated # Check for updates
dart fix --apply # Auto-fix common issues
flutter clean && flutter pub get # Nuclear option for build issuesSupported: Windows, Linux, Android, iOS, macOS
Use platform checks when needed:
import 'dart:io';
if (Platform.isAndroid || Platform.isIOS) {
// Mobile-specific code
} else {
// Desktop-specific code
}- No progression with errors - Fix ALL analyzer errors before next step
- Run
flutter analyzeafter EVERY file change - Don't accumulate errors - RTL text fields - Use
RtlTextFieldexclusively, neverTextField - Icons -
otzaria_iconsfirst,fluentui_system_iconsonly for what it lacks, Material never.OtzariaIconsalways uses plainIcon(...)— neverRtlIcon, it is already RTL.RtlIconis for Fluent icons registered inlib/widgets/misc/rtl_icon.dart(_fluentMirrorMap,_flippableIcons); all others: plainIcon(...). Never add manualTransformon icons. - User messages - Only through
UiSnack, never direct SnackBar - Dialogs - Only through
custom_ui_components(SingleActionDialog, TwoActionsDialog, WarningDialog) - Action buttons - Only
ActionButton.recommended/.neutral/.ghostfromwidgets_exports.dart - Settings cards - Only
SettingsCardfromsettings_card.dart - Color theming - NEVER use hardcoded colors (Colors.red, Colors.blue, etc.), ALWAYS use
Theme.of(context).colorScheme - Hover effects - Remove from ListTile rows with buttons (
hoverColor: Colors.transparent) - No color overrides outside
lib/theme/- NEVER addhoverColor,splashColor,overlayColor, or.withValues(alpha:...)in feature files — define them inlib/theme/only - textDirection - NEVER add
textDirection: TextDirection.rtl(the app's locale sets RTL globally). ONLY addtextDirection: TextDirection.ltrfor inherently LTR content: OS paths, email addresses, version numbers, URLs - Test coverage - Add/update tests for every code change
- Documentation - Document all public APIs in Hebrew
- Cross-platform - Code must work on all supported platforms
- Pre-commit trinity -
analyze+test+format= mandatory - Minimal comments - Few comments, max 2 lines each, for the first-time reader only (explain why / prevent regressions) — never document history. Fix violating comments you encounter
- Settings screen text - Every user-visible string under
lib/settings/goes throughcontext.settingsText('<Hebrew>'), with the Hebrew as the key and the English insettings_en.arb; rundart run tool/generate_settings_l10n.dartafter any change - Guided tour text - Same rule for
lib/tour/: every step title/body and live-tip title/description needs asettings_en.arbentry, and a step'sbodystays a literal (variables go in as placeholders)
- Fixing a bug by adding code instead of finding and removing the root cause
- Patching around a null/error with defensive code without understanding why it occurs
- Asking the user questions that could be answered by reading the code or git history
- Asking multiple separate questions instead of batching all open questions into one message
- Writing a large diff to fix what should be a small bug
- Using
TextFieldinstead ofRtlTextField - Reaching for a Fluent icon when
otzaria_iconsalready has one for it — checkOtzariaIconsfirst - Using a Material or Cupertino icon at all —
lib/is Material-free and must stay that way - Wrapping an
OtzariaIconsicon inRtlIcon— it is drawn RTL already, andrtl_icon_registered_usage_test.dartfails on it - Using
RtlIconfor Fluent icons not registered inlib/widgets/misc/rtl_icon.dart— check first; if not registered, use plainIcon(...) - Forgetting to use
RtlIconfor Fluent icons that are registered inlib/widgets/misc/rtl_icon.dart - Adding
mirrorIconparameter to any widget — FORBIDDEN (removed in commit 3b4d357) - Manual
Transform.scale(scaleX: -1)orTransform.flipon icons — register the icon inrtl_icon.dartinstead - Adding inline comments that explain why
RtlIconorIcon(...)was chosen — the decision rule lives in CLAUDE.md, not in code - Showing messages without
UiSnack - Using custom dialogs instead of
custom_ui_componentsdialogs - Using
ElevatedButton/TextButtondirectly instead ofActionButton.recommended/.neutral/.ghost - Using hardcoded colors instead of
Theme.of(context).colorScheme - Not removing hover effects from ListTile rows with action buttons
- Adding
hoverColor,splashColor,overlayColor, or.withValues(alpha:...)outsidelib/theme/— these belong only in the theme layer - Adding
textDirection: TextDirection.rtlto anyTextwidget — the app's locale already sets RTL globally, this is always redundant - Missing
textDirection: TextDirection.ltron LTR content (OS paths, emails, version numbers, URLs) - Skipping
flutter analyzebefore committing - Running full test suite instead of relevant tests
- Formatting entire project instead of modified files
- Moving to next feature while current code has warnings
- Not testing on multiple platforms
- Hardcoding platform-specific paths
- Creating unnecessary MD files to document changes (CHANGES.md, SUMMARY.md, etc.)
- Adding a bare Hebrew string to a settings widget instead of
context.settingsText(...)— it stays Hebrew in English mode - Using string interpolation inside a
settingsTextkey instead ofargs: - Editing
settings_catalogs.g.dartby hand instead ofsettings_en.arb+ the generator - Opening a dialog from settings without
settingsDialogBuilder— it inherits neither language nor direction - Passing a variable to
settingsTextwithout a case insettings_variable_labels_test.dart— the validator only sees literals - Adding too many comments, long comments (over 2 lines), or comments that document history ("used to be X", "changed in commit Y") instead of explaining why for a first-time reader
Remember: ALWAYS respond in Hebrew!