src/main/resources/fonts: Press Start 2P by The Press Start 2P Project Authors, under the SIL Open Font License 1.1 (licence included as PressStart2P-OFL.txt), and Pokemon Pixel Font (pokemon_pixel_font.ttf) by SpyroSteak, under CC BY-SA 3.0. The interface theme is inspired by the Pokedex in Pokemon Black 2 and White 2.Refer to the guide Setting up and getting started.
The Architecture Diagram given above explains the high-level design of the App.
The following provides a quick overview of the main components and their interactions.
Main components of the architecture
Main and MainApp are in charge of the app launch and shut down. Main is the Java entry point that launches MainApp, which initializes and connects the other components.
MainApp loads user preferences and address-book data through Storage, creates Model and Logic, and then creates UI with the Logic API.MainApp saves the current user preferences through Storage.The bulk of the app's work is done by the following four components:
UI: The UI of the App.Logic: The command executor.Model: Holds the data of the App in memory.Storage: Reads data from, and writes data to, the hard disk.Commons represents a collection of classes used by multiple other components.
How the architecture components interact with each other
The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command delete confirm, right after delete 1 has previewed the first student.
Each of the four main components (also shown in the diagram above),
interface with the same name as the Component.LogicManager, ModelManager, StorageManager and UiManager) that implements the corresponding API interface.For example, the Logic component defines its API in Logic.java and implements it in LogicManager.java. Other components interact with a component through its interface rather than its concrete class, preventing them from coupling to that component's implementation, as illustrated in the following partial class diagram.
The sections below give more details of each component.
The API of this component is specified in Ui.java
UiManager implements Ui and starts the JavaFX interface by creating and showing a MainWindow, then filling its placeholders. It also handles fatal errors during UI initialization. UiManager and MainWindow both keep a reference to Logic.
MainWindow assembles the TuteeDex interface:
DD - MMM, with an English month abbreviation), and the local 24-hour time (HH : MM). A JavaFX Timeline refreshes the clock every second while the main window is shown and stops when it is hidden. The clock can be supplied in tests.ResultDisplay shows either the TOTAL/PAID summary or command feedback, with line breaks replaced by |. Feedback that exceeds the normal display width switches to a wrapping two-line display, with vertical scrolling for additional text. Resizing re-evaluates the available width. Result displays have no tooltip.PersonListPanel displays the filtered ObservableList<Person> obtained from Logic. Its custom PersonListViewCell creates a PersonCard for each non-empty cell, so changes to the observable list update the displayed students.MainWindow puts one of three panels in the list area, based on the ListPresentation of each CommandResult. PersonListPanel is the normal list. SubjectGroupPanel shows list -subjects; it uses SubjectGrouping to group the displayed students under each subject, and each student keeps one shared index. LessonListPanel replaces the normal list when the current sort uses slot/, with one row per student showing that student's next lesson. Both are rebuilt from Logic after each command that changes the list.PersonCard shows a three-digit, zero-padded list index, the student's name, and subjects. After reserving the icon, index, spacing, and insets, width bindings divide the remaining space between the name and subject labels in a 3:2 ratio. JavaFX adds trailing ... only when the text exceeds the label width; the underlying text remains complete. Tooltips expose the full profile and subjects, and accessible text includes the profile details. Grouped and lesson-sorted rows let the card grow while keeping the separate lesson-time label at the right edge.CommandBox accepts commands at the bottom of the window. The FIND button calls prepareFind(), which turns the current input into a find command: blank input becomes find , existing text such as Amy becomes find Amy, and input that already starts with find is kept. It then focuses the input and moves the caret to its end without executing a command.CommandBox also keeps a history of the commands entered in this session. Up shows the previous command and Down the next one; going past the newest command restores the text that was being typed. A command is not added again if it repeats the one before it. The history is not saved when the app closes.exit command close the application window. Help is available through the help command and F1, using a HelpWindow created with MainWindow and reused when requested.MainWindow, CommandBox, ResultDisplay, PersonListPanel, PersonCard, and HelpWindow inherit from the abstract UiPart<T> class. SubjectGroupPanel and LessonListPanel build their JavaFX nodes in code and reuse PersonCard for each student. UiPart loads an FXML layout, sets the controller, and exposes the root JavaFX object through getRoot(). StatusBarFooter and its FXML file remain in the source tree, but the current MainWindow does not instantiate or display a status bar; it is omitted from the diagram above.
The layouts are defined in src/main/resources/view. For example, MainWindow.fxml defines the main layout and loads TuteeDex.css, while PersonListCard.fxml defines the layout for PersonCard. The main window also loads the bundled pixel fonts. HelpWindow has its own FXML layout and stylesheet.
The student-area background is anchored below the summary/feedback panel. One repeating
green tile fills the area at a fixed scale; a separate 80-pixel-high region displays
the original image's dark band without overlaying a second green grid. The outer
VBox disables pixel snapping to avoid a bottom-edge gap from cumulative rounding
at fractional display scales. FIND and exit are image-only buttons with transparent
backgrounds, accessible labels, and tooltips. The application and documentation use
the TuteeDex icon.
HelpWindow renders command formats as single-line JavaFX labels in a scroll pane,
with full text in tooltips. Gradle bundles docs/UserGuide.md as a classpath resource;
the help window reads the inline-code entries on its Format: lines, so the guide
is the single source for the displayed formats, including sorting. Its browser
callback comes from the running MainApp's HostServices, through UiManager
and MainWindow; it does not embed a browser. The close button and Escape call
Stage.close(). MainWindow uses the same pixel close icon but retains its application
exit handler.
The UI depends on Model classes to display Person data and to identify students whose outstanding amount is zero for the PAID count. Commands and access to the filtered list and GUI settings go through Logic.
The following sequence diagram shows submission of a successful list command and the resulting summary:
CommandBox ignores an empty string; otherwise, it passes the input to its CommandExecutor. MainWindow supplies this callback as this::executeCommand, keeping CommandBox independent of Logic.MainWindow calls Logic.execute(commandText) and displays the returned CommandResult feedback. For a successful list command, it then replaces that feedback with the TOTAL/PAID summary. showListSummary() reads the filtered list: TOTAL is its size, and PAID counts students whose outstanding amount equals OutstandingAmount.ZERO. The summary is calculated at startup and after successful list commands; it is not continuously bound to list changes.MainWindow also checks the result's help and exit flags. Help opens or focuses the existing HelpWindow; exit saves the current window size and position through Logic.setGuiSettings(), then hides the help and main windows. The exit button uses the same exit handler, and F1 uses the help handler.CommandBox clears the input. If Logic throws a CommandException or ParseException, MainWindow displays the exception message and rethrows it. CommandBox retains the input and applies its error style, which is removed when the text next changes.The sequence diagram omits error handling, help/exit flag handling, and the observable list's JavaFX cell updates for clarity.
For List commands, text such as Listed N contacts., No contacts match the specified subjects.,
No contacts match the specified tags., and No archived contacts. remains in the CommandResult
and is logged by MainWindow, but the visible result is the TOTAL/PAID banner. The optional -summary
changes the command's returned feedback; it does not opt into a different GUI display.
This applies to plain, filtered, archived and subject-grouped List modes. Empty results show both counts
as zero. Grouped counts use unique students, including subjectless students, rather than grouped rows.
Invalid List input follows the exception path instead: error feedback is shown and the current list remains.
API : Logic.java
The partial class diagram below shows the command-execution boundary of the Logic component.
LogicManager implements Logic and keeps one reference each to AddressBookParser, Model, and Storage.
It parses command text into a Command, executes the command against the model, and saves the address book when
Command#requiresAddressBookSave() returns true. Commands that change no students (view, help, exit, export, list, find, sort, and the
delete, clear and import previews) return false, so they never write the data file.
DeleteCommand represents the concrete command subclasses in this diagram; its note lists other examples.
Command-specific parser classes and utility classes are omitted here. CommandResult carries feedback, the
showHelp and exit flags, and a ListPresentation (CONTACTS, SUBJECT_GROUPS, UNCHANGED or REFRESH)
that tells MainWindow which list panel to show. LogicManager also holds pendingDelete, the deletion
previewed by the previous command, if any.
Deleting a student takes two commands. delete 1 only previews: DeleteCommand checks the index, returns the
student's details with ListPresentation.UNCHANGED, and does not save. LogicManager then stores a
DeleteCommand bound to that exact Person as pendingDelete. Every later command clears pendingDelete before
it is parsed, so only an immediately following delete confirm can use it.
The sequence diagram below follows a successful execute("delete confirm") call right after delete 1.
It assumes that saving succeeds.
How the Logic component works:
LogicManager.execute(commandText) first takes pendingDelete and clears it. It then calls
AddressBookParser.parseCommand(commandText), which creates DeleteCommandParser. For delete confirm, the
parser returns a confirmation DeleteCommand.LogicManager replaces it with the stored pending command. If there was
no preview, it throws a CommandException instead.LogicManager copies the address book first. It then calls execute(model). The
pending command checks that its Person is still displayed, passes it to model.deletePerson(person) and
returns a CommandResult with the success feedback.LogicManager gets the updated address book from Model and passes it to Storage.saveAddressBook().
Only after saving succeeds does it update the remembered view and return the result to the caller.Invalid command syntax produces a ParseException; an out-of-range Delete index produces a CommandException
during the preview, before any student is deleted. These failure paths are omitted from the success sequence. A storage failure
also becomes a CommandException. Before running a command that saves, LogicManager copies the address book,
and if saving fails it puts that copy back with Model#setAddressBook(). The students shown then match the saved
file, and retrying a command such as charge 1 5 does not apply it twice.
Here are the other classes in Logic (omitted from the class diagram above) that are used for parsing a user command:
How the parsing works:
AddressBookParser first separates the command word from its arguments and dispatches the input to the matching command parser. The current command-specific parsers are AddCommandParser, EditCommandParser, DeleteCommandParser, ClearCommandParser, PaidCommandParser, ChargeCommandParser, FindCommandParser, ListCommandParser, SortCommandParser, ArchiveCommandParser, UnarchiveCommandParser, ExportCommandParser, ImportCommandParser, and ViewCommandParser.exit and help) are constructed directly by AddressBookParser. All other supported command words are delegated to a parser that creates the corresponding concrete Command object. The diagram shows these parsers as XYZCommandParser.PaidCommandParser and ChargeCommandParser share BalanceCommandParser, which reads an index and an amount. SortCommandParser passes sort criteria to SortOrderParser. CsvPersonParser, used by ImportCommand, reads one CSV row rather than command text, so it is not part of this diagram.Parser<T extends Command> interface. Prefix-based parsers use CliSyntax and Prefix to identify fields, ArgumentTokenizer to split the input, and ArgumentMultimap to collect repeated values. ParserUtil validates and converts values such as indexes, subjects, tags, notes, names, and contact fields before the command is created.ParseException; the command is not executed.API : Model.java
The Model component,
Person objects (which are contained in a UniquePersonList object).Subject objects. Each Subject owns one required LessonSlot,
which records a DayOfWeek and a LocalTime start time.Person objects selected by the current filter, such as search results, in a separate filtered list. It exposes this list as an unmodifiable ObservableList<Person> that the UI can observe and bind to, so the UI updates when the list changes.UserPrefs object that represents the user’s preferences (the GUI settings, the last list view and the last sort). This is exposed to the outside as a ReadOnlyUserPrefs object.Model represents data entities of the domain, they should make sense on their own without depending on other components)Note: The alternative, arguably more object-oriented, design below keeps a unique list of tags in AddressBook, and each Person references tags from that list. This lets AddressBook maintain one Tag object per unique tag instead of each Person holding its own Tag objects.

API : Storage.java
The Storage interface is implemented by StorageManager, which delegates student-data access to
JsonAddressBookStorage and user-preference access to JsonUserPrefsStorage.
JsonAddressBookStorage reads and writes JsonSerializableAddressBook, which contains zero or more
JsonAdaptedPerson records. Each record stores the student's core profile fields, notes, archive status,
zero or more tags represented by JsonAdaptedTag, and zero or more scheduled subjects represented by
JsonAdaptedSubject.JsonAdaptedSubject stores the subject name, weekly lesson day, and lesson start time. It reconstructs
a Subject with a non-null LessonSlot when data is loaded.toModelType() methods validate stored values and reconstruct the corresponding model objects.
JsonSerializableAddressBook.toModelType() also rejects null student entries and duplicates under the
legacy five-field rule (name, phone, address, start date and amount, compared exactly). It then recovers
duplicates under the current identity rule (balance-only, or differing only in letter case or spacing)
by appending Recovered to later names,
repeating until neither an original nor a recovered identity collides. All original identities are
checked before recovery, so the suffix cannot take a name needed by a later record.
Recovery logs a warning, preserves every balance and all other fields, and does not rewrite the
source file on read. Normal saving persists the recovered names; subsequent loads need no recovery.
Users review the visible suffixes and correct distinguishing identity details with edit; amounts
are never automatically merged.
These conversions explain the Storage component's dependency on the Model component; model classes
are omitted from this diagram.JsonUtil.readJsonFile() rejects a JSON root of null through DataLoadingException, rather than
treating it as a missing file. JsonAddressBookStorage also translates adapter validation failures into
DataLoadingException. MainApp handles that exception by creating an empty in-memory address book;
loading itself does not rewrite the invalid student-data file or load only the valid entries.subjects field load with an empty subject set. Records from v1.4 that store
subjects as plain strings are incompatible because they do not contain a lesson day or start time.
Older records without tags, notes, or archive status still load with empty tags, empty notes, and
archived = false. User preferences are serialized directly without a separate adapter.Classes used by multiple components are in the seedu.address.commons package.
This section describes some noteworthy details on how certain features are implemented.
The diagram shows the sorting types and their main dependencies; UI, storage and comparator parameter lists are omitted for readability.
SortOrder is an immutable, ordered list of field/direction pairs. Its first criterion has the highest
priority. Fields cannot repeat, and an empty order represents the original stored order. The supported
fields are student name, outstanding amount, start date, and upcoming lesson slot. A lesson slot is a
combined day/time criterion; each lesson row associates that schedule with its subject.
SortOrderParser validates criteria arguments such as o/desc n/asc or slot/asc, preserving their order
and independent directions. It rejects malformed arguments, unknown fields, repeated fields, and directions
other than lowercase asc or desc. The criteria model and parser do not mutate profiles or user preferences.
SortCommandParser handles the current-order query and reset, and the registered SortCommand applies
criteria to the session's ModelManager. Saving and restoring the order is described in Restoring the last view.
SortCommand overrides Command#requiresAddressBookSave() so LogicManager does not write profile JSON
for apply, reset or query. A storage permission error therefore cannot make session-only sorting fail.
Other commands keep their existing save behaviour; preference persistence remains separate.
SortComparators compares names without regard to case, amounts numerically, and start dates as annual
month/day values. Invalid existing dates and absent lessons stay last in both directions. LessonRow
references a profile and one of its subjects without duplicating stored data. Upcoming lesson comparisons
use one captured local date/time: a start equal to that time is due now, while a start that has already
passed is compared using its next weekly occurrence. representativeLesson() selects the minimum row under
the requested comparison: nearest for ascending slots and furthest for descending slots. Student-level
criteria are constant within one profile, so the slot direction selects its representative even in a mixed
order. Equal slots retain subject insertion order; subjectless profiles use a null subject.
ModelManager exposes a SortedList over the existing filtered students. The stored address-book list is
not reordered, and complete ties keep the stored order. This sorted list supplies the displayed index used by
View, Edit and Delete. getStudentRows() gives one representative lesson per student in the same order, which
LessonListPanel shows when sorting by slot/. Counts and indexed commands still refer to unique students.
Upcoming lessons use the computer's local time zone. Lesson slots are weekly local times, not events in a
particular time zone. Each command compares all lessons against one reference time, so a command run just as
a lesson starts cannot order the list using two different times. No timer reorders rows while a tutor is
typing an indexed command; running sort or list again refreshes the upcoming lessons.
| Decision | Reason |
|---|---|
SortedList over the filtered students | Changes display order while retaining stored/CSV order and the existing unique-student command indexes. Reordering the address book would mix presentation with data changes. |
| One captured local reference per command | Keeps comparisons consistent at lesson boundaries. Reading the clock inside each comparison could change keys while sorting. |
| One representative row per ungrouped student | Keeps natural indexes and whole-profile commands simple. Subject groups retain every lesson because each subject has its own schedule. |
SubjectGrouping keeps alphabetical subject headings and the printed index of each unique student.
When a sort is active, it maps the already ordered ModelManager#getLessonRows() to positions and uses
those positions to order entries within each group. This API still includes every lesson; it does not
collapse to representatives. Comparisons use the same reference time and criterion rules as the ungrouped
display, but each grouped card retains its own subject and slot. Complete lesson ties explicitly fall back
to stored student positions, independently of representative ordering. A student can appear under several
subjects with the same printed student index; deleting that index removes their whole profile and all lessons.
Without active criteria, grouping retains its original alphabetical student order. Subjectless students
remain part of the unique-student list and counts, but do not produce subject groups.
CommandResult.ListPresentation.REFRESH requests rebuilding the current presentation without selecting
contacts or subject groups. Applying/resetting Sort uses this request; a bare Sort query uses UNCHANGED.
MainWindow remembers the current grouped presentation and rebuilds the appropriate panel after an apply
or reset. View, Help, Export, a bare Sort query and the delete and clear previews preserve the existing panel
and its scroll position.
Find, other List options and successful profile mutations select the ungrouped presentation, as before.
The active sorting criteria remain available through Logic/Model, which is how the last sort is saved (see Restoring the last view).
Subject contains its validated display name and one required LessonSlot. LessonSlot contains a
DayOfWeek and LocalTime, which prevents incomplete subjects inside the model. Add and Edit accept one
ordered s/SUBJECT day/DAY time/TIME triplet per subject. ParserUtil validates the triplet counts and
order, accepts the documented three-letter day names, requires strict 24-hour HH:mm times, and rejects
duplicate subject names case-insensitively. List filters continue to compare only the subject name so that
list s/Maths works regardless of when each student's Maths lesson begins.
ListCommandParser tokenizes both s/ and t/ prefixes together, accepting either category order
and interleaved repeated values. It reuses ParserUtil validation and PersonHasAnyTagPredicate.
ListCommand combines its subject-name match with the supplied tag predicate using AND, then applies
active-student visibility. Each category uses OR internally; an omitted category adds no restriction.
The command updates the existing filtered list, so students appear once and the current sort order
and displayed indexes still apply. Archived-only and subject-grouped modes remain standalone.
ListCommandParser recognises one standalone -summary before, between, or after filter arguments.
It removes only the flag before delegating subject/tag values to the existing validators.
The flag must precede another prefix/summary flag or end the input, so a hyphenated value such as
t/Pre-summary and text inside a multiword value such as t/Needs -summary revision are preserved.
Repeated summary flags and combinations with the standalone -archived or -subjects modes are rejected.
Subject.normalizeName() trims boundary whitespace and collapses runs of ordinary ASCII spaces before
validation and construction; the 1–50 character limit applies to the normalized name. It preserves case
and the lesson slot, and does not permit internal tabs or line breaks. ParserUtil.parseSubjectName()
reuses this rule for Add, Edit, List and CSV import. Equality, hashing, name matching and UI grouping
therefore use consistent names. Each student's subject names remain unique, ignoring case, even when
their lesson slots differ. Different students can share a subject heading while retaining their own slots.
JSON adapters construct the same normalized Subject model. Reading legacy spaced names does not
rewrite the file; subsequent saves serialize canonical names. If legacy entries for one student collide
after normalization, JsonAdaptedPerson rejects the record with IllegalValueException, translated by
storage into DataLoadingException. Loading fails rather than silently losing a lesson. This is an
upgrade constraint: back up and resolve colliding entries before using the file.
Tags are optional descriptive labels, separate from subjects. Add accepts repeated t/TAG values
using ParserUtil.parseTags(). This validates every occurrence before adding the profile, combines
repeated tags, and produces an empty set when no tags are supplied. The shared contract is:
Tag accepts ASCII letters, digits, spaces, and hyphens, and rejects blank values. It trims surrounding
whitespace and stores lowercase text using Locale.ROOT. Internal spaces are preserved.Person.getTags() returns an immutable set. Repeated tags and case variants collapse into one value;
order has no meaning.Person.isSamePerson() compares name, phone, address and start date. Name and address ignore letter
case and repeated spaces, and the start date ignores the case of the month, so amy bee with 10-aug
is the same student as Amy Bee with 10-Aug. The stored spelling is kept as typed. Outstanding amount, tags,
notes, subjects and archive state do not distinguish identity. UniquePersonList, Add, Edit and Import
share this rule. Paid and Charge change only the balance and cannot create an identity collision.
Full names alone need not be unique: different phone, address or start date distinguishes students.Person.equals() and hashCode() include tags, so changes to stored data remain detectable even when
the student identity stays the same.tags array of normalized strings in alphabetical order. A missing or null array loads
as an empty set, preserving compatibility with the previous student-profile schema. Invalid or null
elements reject the stored profile. For example: "tags": ["a-level", "needs revision"].t/ is supplied and keeps the existing tags otherwise. A single
blank t/ clears all tags. list t/TAG filters with PersonHasAnyTagPredicate, which reuses the same
normalization, so t/A-Level matches the stored a-level.view INDEX shows the stored lowercase tags. No tag length or count limit is enforced.Names must start with a Unicode letter and may contain Unicode letters, combining marks, ordinary spaces, hyphens (-), and straight or curly apostrophes (', ’). Examples: Anne-Marie, O'Connor, O’Connor, José (including a combining accent), and 张伟. Digits, control characters, and other punctuation are rejected. Spelling is preserved; surrounding whitespace is trimmed by Add, Edit, and CSV import.
Name.isValidName is shared by ParserUtil.parseName (Add, Edit and CSV import) and
JsonAdaptedPerson (JSON loading). No Unicode normalization is applied. Duplicate equality
continues to compare the supplied spelling; Find continues to match case-insensitive word
prefixes split on whitespace, without transliteration or accent folding. Name sorting keeps
its existing case-insensitive lexical comparison rather than locale collation.
import FILE_PATH [confirm] reads the CSV format written by export, so a file exported from one copy of
TuteeDex can be imported into another. The work is split into three small classes:
CsvReader (in commons.util) turns the text into rows of fields. It follows CsvExporter's quoting rules,
ignores the byte order mark that Excel adds, accepts LF and CRLF, and keeps line breaks inside quoted fields.
Blank lines are kept as blank rows so that row numbers in error messages match what a spreadsheet shows.CsvPersonParser (in logic.parser) turns one row into a Person. It reuses the ParserUtil methods used by
add, so imported values follow exactly the same rules and produce the same error messages. Because Subject
holds a LessonSlot, a subject cell such as Maths (Mon 09:00); Physics (Tue 10:00) is split on semicolons and
each part is parsed into a name, a day and a start time, matching Subject#toString(), which the export uses.ImportCommand checks the header against CsvExporter.HEADER, parses every row, and collects all problems
before changing anything.Duplicates. The model already rejects two students with the same identity (Person#isSamePerson()). A row
that is equal in every field to a stored student, or to an earlier row, is skipped, which makes importing the same
file twice harmless. A row with the same identity but different details is an error, because adding it would break
the uniqueness rule and silently keeping either version could lose data.
The activity diagram below shows what one import command does. Running it without confirm is the preview;
the tutor confirms by running it again with confirm, or cancels by doing nothing.
All or nothing. Without confirm, the command only returns a preview. With confirm, it copies the current
AddressBook, adds every new student to the copy and then calls Model#setAddressBook() once. Existing students
are kept, and if any row is invalid the command throws before the copy is made, so the stored data never ends up
half imported. The filter is then reset to active students so the new students are visible.
Data sandbox. We chose to let import read only files inside the folder TuteeDex runs from, so that a
mistyped or unexpected path cannot make the app read unrelated files on the computer. ImportCommand resolves the path against the folder TuteeDex runs from, the same folder export writes to,
and rejects any path that ends up outside it, including .. steps and links that lead out of the folder.
Alternatives considered. Keeping the preview in memory and confirming it with a separate command would avoid
reading the file twice, but it adds state that can go stale if the file or the address book changes in between.
Reading and checking the file again on confirm is simpler and always uses the current data.
When TuteeDex opens, it shows the same list the user was looking at when they closed it, in the same order,
such as the subject groups from list -subjects sorted by sort o/desc.
What is saved. UserPrefs has two strings. lastViewCommand is the list or find command that last decided
which students are shown, and lastSortCommand is the sort command that last set the order. An empty string
means the normal list or the default order. Both are saved with the GUI settings in preferences.json, because
they are preferences about the display rather than student data.
When it changes. After each successful command, LogicManager#execute() calls saveLastView():
SortCommand that applies criteria saves its own command text, and sort reset clears the saved order.
A bare sort only reports the order (its result is ListPresentation.UNCHANGED), so nothing changes.
Sorting keeps the current presentation, so it never changes the saved view.ListCommand or FindCommand saves its own command text.ListPresentation.UNCHANGED (view, help, export, and the delete and clear
previews), or that exits the app,
keeps the saved view, because the display does not change.LogicManager#execute() reads
Model#getFilteredPersonListPredicate() before the command runs, and saveLastView() compares it with the
filter afterwards. Commands that reset the filter, such as add, archive and import, clear the saved
view. delete and edit keep the filter, so the saved view is kept. The saved view is also
cleared when the subject groups are replaced by the contact list, which LogicManager tracks from each result's
ListPresentation. The saved order stays, because sorting carries over to the normal list.A command that fails throws before saveLastView() runs, so it never changes the saved view. MainApp#stop()
already writes UserPrefs to disk when the app closes, so no new storage code is needed.
Restoring. At the end of MainWindow#fillInnerParts(), restoreLastView() runs the saved sort command and
then the saved view command through the normal executeCommand() path, so the list, the subject groups and the
result box are updated in the same way as when the user types the commands. The sort runs first so that the view
is built in the saved order. The preferences file can be edited by hand, so LogicManager#getLastSortCommand()
and LogicManager#getLastViewCommand() parse the saved text and return it only if it is a sort command, or a
list or find command. Anything else, such as delete 1, is logged and ignored.
A restored find can show no students, for example after its only match was deleted, so restoreLastView() puts
MainWindow.MESSAGE_LAST_VIEW_RESTORED before the find result. A restored list shows the student counts instead
of the result box, so it needs no message.
Like sort, list and find override Command#requiresAddressBookSave() to return false, because they
only change the display. Otherwise, if the data file failed to load and the app started empty, replaying the
saved view would write that empty address book over the user's file. help, view, export, exit, and the
delete, clear and import previews do the same, because they change no students either.
Alternatives considered. Saving the filter itself is not practical, because the filters are Predicate
objects built from lambdas and cannot be written to JSON without a separate data form for every kind of filter.
Saving the command text reuses the existing parsers, so new list options are restored without extra code.
Making delete reset the filter would also keep the saved view and the screen in step, but the index numbers on
screen would change after every deletion, so a following delete 2 could remove a different student.
Person has an archived flag, which is saved in the JSON file and in the CSV export. Records without the flag
load as active. archive INDEX and unarchive INDEX pick the student from the currently displayed list, like
delete, and replace the profile with Person#withArchived() through Model#setPerson(). Archiving an already
archived student, or unarchiving an active one, is rejected with a CommandException.
Model.PREDICATE_SHOW_ACTIVE_PERSONS hides archived students. Plain list, the subject and tag filters, find,
list -subjects and the TOTAL/PAID counts all apply it, and add, archive and unarchive reset the
filter to it. Edit keeps the current filter. Only list -archived shows the archived students instead.
Alternatives considered. Moving archived students to a second list in AddressBook would keep them out of
every filter automatically, but edit, export, import and duplicate checks would then have to look in two
places. A flag on Person keeps one list and one duplicate check.
Edits keep the existing predicate and sort order. ModelManager#setPerson() updates the filtered and sorted
lists and invalidates rendered rows; no index snapshot is retained. An edited student can disappear from the
filter or move after a sort-key change. Edit feedback warns when that student's displayed index changes.
Archived edits keep the archived filter and archive flag. Successful edits from subject groups still switch
to contacts and clear the saved grouped presentation, as other contact-changing commands do.
DeleteCommand previews with ListPresentation.UNCHANGED and does not require a save.
After a successful preview, LogicManager holds a confirmation bound to the exact immutable Person.
Only an immediately following delete confirm uses it. Every command attempt clears the pending
confirmation before parsing, so filter/sort changes, malformed input, and failures cancel it.
Pending confirmations exist only in memory. Confirmed deletion uses the normal save path;
LogicManager restores the address book snapshot on a save error, as for every saving command, and
updates the saved view only on success.
Binding the confirmation to the Person rather than to the index means a list that changed in between
cannot make delete confirm remove a different student. The flow is shown in the Logic component.
Alternatives considered. A delete INDEX confirm form in one command would be shorter, but it would let a
mistyped index delete the wrong student without the tutor seeing who it is. clear keeps its own one-command
clear confirm, because it does not depend on an index.
The paid and charge commands use the index from the currently displayed student list. Their parsers validate
the index and amount before creating a command. paid INDEX replaces the balance with zero, while
paid INDEX AMOUNT subtracts a positive amount. charge INDEX AMOUNT adds a positive amount. Amount arithmetic
is performed by the immutable OutstandingAmount value object, so balances remain nonnegative and limited to two
decimal places. Payments greater than the current balance are rejected with a command error before the profile
is modified.
ParserUtil#parseOutstandingAmount() rejects any entered amount above OutstandingAmount.MAXIMUM
(SGD 1,000,000.00), and ChargeCommand rejects a charge whose result would go above it. The limit is checked only
on input, not when loading the data file, so a file with a larger balance from an older version still loads.
BalanceCommandParser checks the format of a payment or charge amount itself, so its error talks about the amount
rather than the stored balance.
Target user profile:
Value proposition: Organizes the chaos that comes with tutoring many students privately. Track each student's contact details, lesson schedule, and fee payments in one place, so private tutors spend less time on admin and less time chasing missed payments.
Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *
| Priority | As a … | I want to … | So that I can… |
|---|---|---|---|
* * * | new user | see usage instructions | refer to instructions when I forget how to use the App |
* * * | private tutor | add a student | keep track of the students I teach |
* * * | private tutor | view a list of all my students | see all my students in one place |
* * * | private tutor | view a student's profile | quickly access the student's information |
* * * | private tutor | edit a student's details | keep my records accurate when information changes |
* * * | private tutor | delete a student | remove records that are no longer needed |
* * * | private tutor | see which students still owe me money | follow up on unpaid fees in a timely manner |
* * * | private tutor | search for a student by name | quickly find a student profile without scrolling |
* * * | private tutor | view the current outstanding balance on a student's profile | know exactly how much they owe me |
* * * | private tutor | record each student's subjects with a weekly lesson day and time | know when I teach each student |
* * | private tutor | record a student's address | know where they live, to travel to their home and give lessons / chase money |
* * | private tutor | import students from a file exported by TuteeDex | move my students to another computer without typing them in again |
* * | private tutor | have TuteeDex open on the list I was last using | continue where I left off without typing the same list, find or sort again |
* * | private tutor | list students by subject | prepare for lessons of one subject at a time |
* * | private tutor | group students under their subjects | see who I teach for each subject at a glance |
* * | private tutor | tag students, such as needs revision, and list them by tag | focus on students who need the same kind of attention |
* * | private tutor | add a note to a student | remember things about the student that do not fit other fields |
* * | private tutor | sort students by name, balance, start date or next lesson | put the most urgent students first |
* * | private tutor | record payments and new charges | keep each student's outstanding balance up to date |
* * | private tutor | archive a student who has stopped lessons | hide them from my active list without losing their records |
* * | private tutor | export my students to a CSV file | keep a copy of my records outside the app |
* | private tutor | clear all students after a confirmation | start over without deleting the data file by hand |
(For all use cases below, the System is TuteeDex and the Actor is the private tutor.)
Use case: UC01 - Add a student profile
MSS
Tutor requests to add a student profile with the student's details.
TuteeDex validates the supplied details.
TuteeDex saves the profile and confirms that it was added.
Use case ends.
Extensions
2a. One or more supplied details are invalid or missing.
2a1. TuteeDex shows the relevant error and does not add the profile.
Use case resumes at step 1.
2b. A profile with the same identity (name, phone, address and start date) already exists.
2b1. TuteeDex rejects the duplicate and does not add the profile.
Use case ends.
Use case: UC02 - Locate and view a student profile
MSS
Tutor requests to list all students or search for a student by name.
TuteeDex displays the matching students with their indexes, names and subjects.
Tutor requests to view one student using the index shown in the displayed list.
TuteeDex displays the student's full profile, including contact, subject, fee, tag and note details.
Use case ends.
Extensions
2a. No students match the search, or the list is empty.
2a1. TuteeDex informs the tutor that there are no matching students.
Use case ends.
3a. The selected index is invalid or outside the displayed list.
3a1. TuteeDex shows an error and does not display a profile.
Use case resumes at step 2.
Use case: UC03 - Update a student profile
MSS
Tutor searches for a student by name.
TuteeDex displays matching students.
Tutor selects a student using the index shown in the displayed list and supplies the details to update.
TuteeDex validates the supplied details and applies the changes while preserving fields the tutor did not specify.
TuteeDex saves the changes and confirms the update. The displayed list keeps its current search and order.
Use case ends.
Extensions
3a. The tutor supplies no details to update.
3a1. TuteeDex shows an error and leaves the profile unchanged.
Use case resumes at step 2.
4a. The index or one or more supplied details are invalid.
4a1. TuteeDex shows the relevant error and leaves the profile unchanged.
Use case resumes at step 2.
4b. The updated profile would have the same identity as another student.
4b1. TuteeDex rejects the update as a duplicate and leaves the profile unchanged.
Use case resumes at step 2.
4c. The supplied details are the same as the stored details.
4c1. TuteeDex informs the tutor that no changes were made.
Use case ends.
5a. The updated student no longer matches the current search, or moves because of the current sort order.
5a1. TuteeDex warns the tutor that the displayed indexes have changed.
Use case ends.
Use case: UC04 - Delete a student profile
MSS
Tutor requests to list all students or search for a student by name.
TuteeDex displays the matching students.
Tutor requests to delete a student using the index shown in the displayed list.
TuteeDex previews the selected student's details without changing or saving data.
Tutor confirms the deletion as the next command.
TuteeDex deletes exactly the previewed profile and saves the change.
Use case ends.
Extensions
2a. No students match the search, or the list is empty.
2a1. TuteeDex informs the tutor that there are no matching students.
Use case ends.
3a. The selected index is invalid or outside the displayed list.
3a1. TuteeDex shows an error and leaves all profiles unchanged.
Use case resumes at step 2.
5a. Tutor enters any other command instead.
5a1. TuteeDex cancels the pending deletion and runs the other command.
Use case ends.
6a. Saving fails.
6a1. TuteeDex restores the profile and shows an error.
Use case ends.
Use case: UC05 - Record a payment or a new charge
MSS
Tutor requests to list or search for students.
TuteeDex displays the matching students.
Tutor records a payment, or a charge for new lessons, for a student using the index shown in the displayed list.
TuteeDex updates the student's outstanding balance, saves it and shows the new balance.
Use case ends.
Extensions
3a. The index is invalid, or the amount is not a positive amount with at most two decimal places.
3a1. TuteeDex shows an error and leaves the balance unchanged.
Use case resumes at step 2.
3b. The payment is larger than the outstanding balance, or the charge would take the balance above SGD 1,000,000.00.
3b1. TuteeDex rejects the amount and leaves the balance unchanged.
Use case resumes at step 2.
3c. Tutor records a payment without an amount.
3c1. TuteeDex treats it as payment of the full outstanding balance.
Use case resumes at step 4.
Use case: UC06 - Archive a student who has stopped lessons
MSS
Tutor requests to list students.
TuteeDex displays the active students.
Tutor requests to archive a student using the index shown in the displayed list.
TuteeDex marks the student as archived, saves it and removes the student from the active list.
Use case ends.
Extensions
3a. The index is invalid or the student is already archived.
3a1. TuteeDex shows an error and leaves the student unchanged.
Use case resumes at step 2.
Use case: UC07 - Unarchive a student who resumes lessons
MSS
Tutor requests to list archived students.
TuteeDex displays the archived students.
Tutor requests to unarchive a student using the index shown in the displayed list.
TuteeDex marks the student as active, saves it and shows the student in the active list again.
Use case ends.
Extensions
2a. There are no archived students.
2a1. TuteeDex informs the tutor that there are no archived students.
Use case ends.
3a. The index is invalid or the student is already active.
3a1. TuteeDex shows an error and leaves the student unchanged.
Use case resumes at step 2.
Use case: UC08 - Restore students from an exported file
Preconditions: The tutor has a CSV file exported by TuteeDex in the folder TuteeDex runs from.
MSS
Tutor requests to import the file.
TuteeDex checks every row and previews the students that would be added, without changing any data.
Tutor confirms the import.
TuteeDex adds all new students together, saves them and reports how many were imported.
Use case ends.
Extensions
1a. The file is missing, is not a CSV file, or is outside the folder TuteeDex runs from.
1a1. TuteeDex shows an error and imports nothing.
Use case ends.
2a. One or more rows are invalid, or conflict with a stored student.
2a1. TuteeDex lists the problem rows and imports nothing.
Use case ends.
2b. Every student in the file is already in TuteeDex.
2b1. TuteeDex reports that there is nothing new to import.
Use case ends.
3a. Tutor does not confirm the import.
3a1. TuteeDex imports nothing.
Use case ends.
list, instead of clicking.export writes one and import reads one.DD-MMM format such as 19-Sep. It is part of a student's identity and can be used for sorting.list command. TOTAL is the number of students in the displayed list, and PAID is how many of them have an outstanding balance of SGD 0.00.list -subjects.needs revision, used to find and filter students. Tags are separate from subjects.o/desc for largest balance first.list -archived shows them, and unarchive makes them active again.Given below are instructions to test the app manually.
Note: These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.
Initial launch
Download the JAR file and copy it into an empty folder.
Open a terminal in that folder and run java -jar "TuteeDex.jar".
Expected: The GUI opens with a set of sample students. The window size may not be optimal.
Saving window preferences
Resize the window to an optimal size. Move the window to a different location. Close the window.
Relaunch the app with the same command.
Expected: The most recent window size and location are retained.
Use a fresh copy of the app in an empty folder, and run clear confirm first.
add n/Amy Bee a/Block 1 p/11111111 d/10-Aug s/Maths day/MON time/09:00, then view 1.Amy Bee's profile has been added to TuteeDex!. View shows Outstanding amount: SGD 0.00,
because o/ was omitted.add n/amy bee a/block 1 p/11111111 d/10-aug s/Physics day/TUE time/10:00 o/5.This contact already exists in TuteeDex. Letter case and extra spaces do not make a new student.add n/Bob Choo a/Block 2 p/22222222 d/19-Sep s/Physics day/TUE time/10:00 o/abc.add n/Bob Choo a/Block 2 p/22222222 d/19-Sep s/Physics day/TUE time/10:00 x/what.add usage. No student is added.Use the test copy above, and add
add n/Bob Choo a/Block 2 p/22222222 d/19-Sep s/Physics day/TUE time/10:00 o/120.50 and
add n/Cara Dee a/Block 3 p/33333333 d/20-Oct s/Maths day/WED time/11:00.
find bo.Found 1 contact. and only Bob is listed. A keyword matches the start of any word in a name, ignoring case.find am cara.Found 2 contacts. Amy and Cara are listed, because a student matches if any keyword matches.find ee.No contacts match the specified name keywords. The middle or end of a word does not match.list, archive 3, then find Cara.list -archived and
unarchive 1 to restore Cara.Using the students above:
list, then view 2.Outstanding amount: SGD 120.50 and Subjects: Physics (Tue 10:00).
The list does not change.view 0, then view 9.Deleting a student while all active students are being shown
Prerequisites: List all active students using the list command, with multiple students in the list.
Test case: delete 1
Expected: The first student's details and a confirmation prompt appear. The list and data file are unchanged.
Test case: delete confirm immediately after the preview
Expected: Only the previewed student is deleted. Restart and check that the deletion persisted.
Test case: delete 1, then sort n/desc, then delete confirm
Expected: Confirmation is rejected; no student is deleted. Repeat with find, list -archived,
list, an invalid command, or delete 1 confirm between preview and confirmation.
Test case: Preview and confirm from a filtered, sorted, or archived list
Expected: The preview identifies the student at the displayed index; confirmation deletes only that student.
Restart after a preview alone and try delete confirm: it is rejected.
Test case: Make the data path unwritable after a preview, then delete confirm
Expected: A save error appears and the student remains. Restore write access, preview and confirm again;
restart and check that only that student was deleted.
Test case: delete 0
Expected: No student is deleted. The result box shows error details.
Other incorrect delete commands to try: delete, delete x, ... (where x is larger than the list size)
Expected: Similar to previous.
Use a fresh copy of the app in an empty folder so these checks do not affect real student records.
clear confirm in this test copy, then add three profiles:
add n/Amy Bee a/Block 1 p/11111111 d/10-Aug s/Maths day/MON time/09:00 t/A-Level,
add n/Bob Choo a/Block 2 p/22222222 d/19-Sep s/Physics day/TUE time/10:00 t/Needs revision, and
add n/Cara Dee a/Block 3 p/33333333 d/20-Oct s/Maths day/WED time/11:00.find Nobody, then list t/A-Level t/Needs revision.list t/NEEDS REVISION t/needs revision, then view 1.list t/revision.list.find Bob, then try list t/, list t/A-Level t/, list t/A-Level x/value, and list T/A-Level.list t/A-Level and list.list, list -summary, list t/A-Level, and list -summary t/A-Level.Using the profiles above:
list -summary s/Maths, list s/Maths -summary, and list s/Maths -summary s/Maths.list -summary t/A-Level t/Needs revision, list t/A-Level -summary t/Needs revision,
and list t/A-Level t/Needs revision -summary.find Bob, then try list t/A-Level -summary -summary, list t/ -summary,
list -subjects -summary, and list -archived -summary.Use the three profiles from the preceding tag-list test in a disposable test copy.
find Nobody, then list s/Maths s/Physics t/A-Level t/Needs revision.list t/Needs revision s/Maths s/Physics.list -summary s/Maths t/Needs revision.sort n/desc, then list s/Maths s/Physics t/A-Level t/Needs revision, and view 1.list s/Maths t/, list t/A-Level s/, list -archived s/Maths t/A-Level, and
list -subjects t/A-Level s/Maths.Use a fresh copy of the app in an empty folder. Run clear confirm, then
add n/Amy Bee a/Block 1 p/11111111 d/10-Aug s/Maths day/MON time/09:00,
add n/Bob Choo a/Block 2 p/22222222 d/19-Sep s/Physics day/TUE time/10:00 and
add n/Cara Dee a/Block 3 p/33333333 d/20-Oct s/Maths day/WED time/11:00 s/Physics day/FRI time/16:00.
list s/maths.list s/Chemistry.list -subjects.list -subjects x.Use a fresh test copy of the JAR in an empty writable folder.
clear confirm, then add n/Amy Bee a/Block 1 p/11111111 d/10-Aug s/Maths day/MON time/09:00 t/A-Level
and add n/Bob Choo a/Block 2 p/22222222 d/19-Sep s/Physics day/TUE time/10:00 o/120.50.
Archive Bob with archive 2.find Nobody, then list -archived.view 1.list, list s/Physics, and list t/A-Level.list -summary counts active students only.list -archived, then try list -archived -summary and list t/A-Level -archived.list -archived.unarchive 1 and repeat
list -archived; expect an empty list with TOTAL 0 / PAID 0.Use a fresh copy of the app in an empty folder so these checks do not affect real student records.
clear confirm in this test copy to remove sample profiles, then add two test profiles:
add n/Amy Bee a/Block 1 p/11111111 d/10-Aug s/Maths day/MON time/09:00 o/20 and
add n/Bob Choo a/Block 2 p/22222222 d/19-Sep s/Physics day/TUE time/10:00 o/600.find Bob, then edit 1 p/91234567 o/20.50.Edited contact: Bob Choo. Only Bob remains displayed, and Bob's name, address, and start date
remain unchanged.view 1.91234567 and outstanding amount SGD 20.50.find Bob, then edit 1 o/20.5.No changes made to contact: Bob Choo. Only Bob remains displayed.edit 1 p/87654321 o/-1, then edit 1 o/.edit 1 o/20 o/30 too; it should show the repeated-field error.edit 1 n/Amy Bee a/Block 1 p/11111111 d/10-Aug o/20.00.This contact already exists in TuteeDex. Bob remains unchanged in the search results.edit 1 o/0.Edited contact: Bob Choo. Only Bob remains displayed, and view 1 shows SGD 0.00.view 1.91234567 and outstanding amount is still SGD 0.00.To check the next indexed action, run clear confirm, then
add n/Zed Test a/Block 9 p/99999999 d/01-Jan s/Maths day/MON time/09:00 o/10 and
add n/Amy Test a/Block 8 p/88888888 d/01-Jan s/Maths day/MON time/09:00 o/10. Run sort n/asc,
find Zed, edit 1 notes/Updated, then charge 1 5. Only Zed should remain visible and owe SGD 15.00;
Amy must still owe SGD 10.00. Run edit 1 n/Yan Test: the search becomes empty and feedback warns
about changed indexes. Restart: the same search and sort should be restored with no matches.
Use a fresh copy of the app in an empty folder. Inspect saved tags in data/addressbook.json
after successful edits; these checks do not require changes to the profile-card display.
clear confirm, then add
add n/Amy Bee a/Block 1 p/11111111 d/10-Aug s/Maths day/MON time/09:00 t/Old notes/Keep note and
add n/Bob Choo a/Block 2 p/22222222 d/19-Sep s/Physics day/TUE time/10:00.find Amy, then edit 1 t/A-Level t/Needs revision t/a-level.Edited contact: Amy Bee, and only Amy remains displayed.
Amy's stored tags are a-level and needs revision; old is removed and the repeated tag is combined.
view 1 still shows subject Maths and notes Keep note, with all five core fields unchanged.find Amy, then edit 1 t/needs revision t/A-LEVEL.No changes made to contact: Amy Bee. Only Amy remains displayed.edit 1 p/87654321 t/A-Level t/ and edit 1 T/A-Level.edit 1 t/, then find Amy and edit 1 t/ again.Use a fresh copy of the app in an empty folder so these checks do not affect real student records.
clear confirm, then add
add n/Amy Bee a/Block 1 p/11111111 d/10-Aug s/Maths day/MON time/09:00 t/A-Level notes/Amy note and
add n/Bob Choo a/Block 2 p/22222222 d/19-Sep s/English day/TUE time/10:00 o/600 t/Needs revision notes/Bob note.find Bob, then edit 1 s/Physics day/WED time/15:30 s/H2 Chemistry day/FRI time/17:00.Edited contact: Bob Choo, and only Bob remains displayed.
view 1 shows Subjects: Physics (Wed 15:30), H2 Chemistry (Fri 17:00) and notes Bob note; the five core fields and tags are preserved.find Bob, then edit 1 s/h2 chemistry day/FRI time/17:00 s/physics day/WED time/15:30.No changes made to contact: Bob Choo. Only Bob remains displayed.
view 1 retains the original subject spelling and order from step 2.edit 1 s/, edit 1 s/Maths day/MON, and edit 1 p/87654321 t/A-Level s/--- day/MON time/09:00.edit 1 n/Amy Bee a/Block 1 p/11111111 d/10-Aug o/0 s/Physics day/WED time/15:30.This contact already exists in TuteeDex. Different subjects, tags, and notes do not bypass duplicate checks.edit 1 p/87654321, then exit and restart from the same folder.view 1 shows Bob's updated phone and still lists both scheduled subjects, with his original notes.Use a fresh copy of the app in an empty folder.
add n/Amy Bee p/11111111 a/Block 1 d/10-Aug s/H2 Maths day/MON time/09:00.view 1 shows H2 Maths (Mon 09:00). Both list s/H2 Maths and
list s/h2 maths find Amy.edit 1 s/h2 maths day/MON time/09:00.edit 1 s/H2 Maths day/MON time/09:00 s/h2 Maths day/TUE time/10:00.add n/Bob Choo p/22222222 a/Block 2 d/19-Sep s/h2 maths day/TUE time/10:00,
then run list -subjects.Use a fresh copy of the app in an empty folder so these checks do not affect real student records.
clear confirm, then add
add n/Amy Bee a/Block 1 p/11111111 d/10-Aug s/Maths day/MON time/09:00 t/A-Level notes/Original note and
add n/Bob Choo a/Block 2 p/22222222 d/19-Sep s/Physics day/TUE time/10:00.find Amy, then edit 1 notes/Revise fractions, graphs & 中文!.Edited contact: Amy Bee, and only Amy remains displayed.
view 1 shows Notes: Revise fractions, graphs & 中文!; all other profile fields are unchanged.find Amy, then repeat edit 1 notes/Revise fractions, graphs & 中文!.No changes made to contact: Amy Bee. Only Amy remains displayed.edit 1 notes/One notes/Two, edit 1 notes/Replacement p/invalid, and edit 1 notes/Review x/value.edit 1 notes/Call parent p/87654321.Call parent and her phone becomes 87654321.
A recognized prefix starts another field rather than becoming literal note text.find Amy, then edit 1 notes/, find Amy, and edit 1 notes/ again.Notes: value.87654321, and her subjects and tags remain unchanged.help, or use F1. Expect the command-format window.add.
Expect all commands to be reachable and the full format to appear in a tooltip.here link. Expect the online User Guide in the default browser.view 1 for an existing student. Expect long feedback to wrap into a two-line
display; scroll vertically to read the remainder. Hovering must not cover it with a tooltip.list to restore the TOTAL/PAID summary.... only where text
does not fit. Widening the window reveals more text; hover tooltips retain the full text.list -subjects. Expect lesson days and times at the right edge of each row.Alex in the input. Expect find Alex, focused
and ready to edit or submit; the button must not execute the search itself.Use a fresh copy of the app in an empty folder so these checks do not affect real student records.
clear confirm, then add
add n/Amy Bee p/11111111 a/Block 1 d/10-Aug o/2 s/Maths day/MON time/09:00 s/Physics day/FRI time/10:00
and add n/Bob Choo p/22222222 a/Block 2 d/01-Jan o/10 s/Maths day/TUE time/11:00.sort o/desc n/asc.001 and Amy is row 002. view 1 shows Bob's full profile.sort slot/asc, then sort slot/desc.view 1 still lists all of that student's subjects.list -subjects, then sort slot/asc n/asc.sort reset, then list.sort o/desc,n/asc, sort n/up, sort n/asc n/desc, and sort RESET.Use a fresh copy of the app in an empty folder so these checks do not affect real student records.
clear confirm, then add
add n/Amy Bee a/Block 1, #01-01 p/11111111 d/10-Aug s/Maths day/MON time/09:00 t/A-Level notes/Revise "fractions"
and add n/Bob Choo a/Block 2 p/22222222 d/19-Sep s/Physics day/TUE time/10:00 o/120.50. Run archive 2.export and note the file name in the result, for example contacts-20261024-175424-176.csv.clear confirm, then import contacts-20261024-175424-176.csv.import contacts-20261024-175424-176.csv confirm.Imported 2 student(s) .... Amy is listed, and view 1 shows Amy's
subject Maths (Mon 09:00), tag and note exactly as before. Then list -archived shows Bob.No new students to import..., and the list does not change.123 and Bob's subjects to Physics, and save it as a
new .csv file. Import it with confirm.import missing.csv, import notes.txt and import with no path.export.Exported N contact(s) to ..., where N counts all students, including archived ones. The path
ends with a file named like contacts-20261102-221836-828.csv (date, time and milliseconds) in the folder
the app runs from. The list does not change.export again.Use a fresh copy of the app in an empty folder.
clear confirm, then add n/Amy Bee a/Block 1 p/11111111 d/10-Aug s/Maths day/MON time/09:00 o/50.charge 1 20.50.Amy Bee's outstanding balance is now SGD 70.50.paid 1 0.50, then paid 1 100.SGD 70.00. The second is rejected because it is more than the balance.paid 1.SGD 0.00, and list shows PAID 1.charge 1, charge 1 0, charge 1 -5, paid 1 1.234 and paid 2.SGD 0.00.clear confirm, then add n/Amy Bee a/Block 1 p/11111111 d/10-Aug s/Maths day/MON time/09:00 and
add n/Bob Choo a/Block 2 p/22222222 d/19-Sep s/Physics day/TUE time/10:00. Run archive 2.Archived Bob Choo., and only Amy remains in the list.find Bob.list -archived, then archive 1.Bob Choo is already archived.unarchive 1.Unarchived Bob Choo., and both students are in the active list again.archive 0, archive x and archive 9.clear.clear 3 and clear Confirm.clear confirm.Deleted all N student(s) from TuteeDex., and list and list -archived are both empty.list -subjects, then close TuteeDex with exit. Open it again.list -subjects.find Alex, close the window and open TuteeDex again.Alex are listed.list -archived, then view 1, then exit. Open TuteeDex again.view does not change the saved view.list -subjects, then add any student with add, then exit. Open TuteeDex again.preferences.json, change lastViewCommand to "delete 1" and open TuteeDex.sort o/desc n/asc, then list -subjects, then exit. Open TuteeDex again.sort reset, then exit. Open TuteeDex again.find with the name of one student, then delete 1, then delete confirm, then exit. Open TuteeDex again.Use a disposable copy of the JAR in an empty folder. Close the app before editing files.
add n/Amy Bee a/Block 1 p/11111111 d/10-Aug s/Maths day/MON time/09:00, then exit. Relaunch.data/addressbook.json in a text editor, replace its whole content with null, save and relaunch.null.data/addressbook.json, then relaunch.