{ list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well }
Refer to the guide Setting up and getting started.
The Architecture Diagram given above explains the high-level design of the App.
Given below is a quick overview of main components and how they interact with each other.
Main components of the architecture
Main (consisting of classes Main and MainApp) is in charge of the app launch and shut down.
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 support@adafruit.com.
Each of the four main components (also shown in the diagram above),
interface with the same name as the Component.{Component Name}Manager classFor example, the Logic component defines its API in the Logic.java interface and implements its functionality using the LogicManager.java class which follows the Logic interface. Other components interact with a given component through its interface rather than the concrete class, as illustrated below.
The sections below give more details of each component.
The API of this component is specified in Ui.java
The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, PersonListPanel, StatusBarFooter etc. All these, including the MainWindow, inherit from the abstract UiPart class which captures the commonalities between classes that represent parts of the visible GUI.
The UI component uses the JavaFx UI framework. The layout of these UI parts are defined in matching .fxml files that are in the src/main/resources/view folder. For example, the layout of the MainWindow is specified in MainWindow.fxml
The UI component
Logic component.Model data so that the UI can be updated with the modified data.Logic component, because the UI relies on the Logic to execute commands.Model component, as it displays Person object residing in the Model.API : Logic.java
Here's a (partial) class diagram of the Logic component:
The sequence diagram below illustrates the interactions within the Logic component, taking execute("delete support@adafruit.com") API call as an example.
Note: The lifeline for DeleteCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
How the Logic component works:
Logic is called upon to execute a command, it is passed to an AddressBookParser object which in turn creates a parser that matches the command (e.g., DeleteCommandParser) and uses it to parse the command.Command object (more precisely, an object of one of its subclasses e.g., DeleteCommand) which is executed by the LogicManager.Model when it is executed (e.g. to delete a person).Model) to achieve.CommandResult object which is returned back from Logic.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 class creates an XYZCommandParser (XYZ is a placeholder for the specific command name e.g., AddCommandParser) which uses the other classes shown above to parse the user command and create a XYZCommand object (e.g., AddCommand) which the AddressBookParser returns back as a Command object.XYZCommandParser classes (e.g., AddCommandParser, DeleteCommandParser, ...) inherit from the Parser interface so that they can be treated similarly where possible e.g, during testing.add support warnings which are generated at 2 levels:
Parser and returned within the ParseResult object.Model components's findSimilarXYZMatch methods (e.g. Model#findSimilarNameMatch) and returned within the CommandResult object.API : Model.java
The Model component
Person objects (which are contained in a UniquePersonList object).Product objects (which are contained in a UniqueProductList object).Person/Product objects as a separate filtered list which is exposed as an unmodifiable ObservableList<Person>/ObservableList<Product>.VersionedVendorVault object that represents the current state of the address book and inventory data, and supports undo/redo operations on it.Alias objects (which are contained in a AliasList object).UserPref object that represents the user’s preferences. This is exposed as a ReadOnlyUserPref object.Archived records are kept in the same data structures rather than moved to a separate list:
Person is considered archived when its tag set contains the reserved "archived" tag. Person#archive() / Person#restore() return new immutable copies with the tag added or removed.Product carries a dedicated boolean isArchived field. Product#archive() / Product#restore() return new immutable copies with the flag toggled.Note: An alternative (arguably, a more OOP) model is given below. It has a Tag list in the AddressBook, which Person references. This allows AddressBook to only require one Tag object per unique tag, instead of each Person needing their own Tag objects.

API : Storage.java
The Storage component
AddressBookStorage, InventoryStorage, AliasStorage and UserPrefStorageModel component (because the Storage component's job is to save/retrieve objects that belong to the Model)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 undo/redo mechanism is facilitated by VersionedVendorVault. It extends VendorVault with an undo/redo history, stored internally as an vendorVaultStateList, stateActionSummaryList and currentStatePointer. Additionally, it implements the following operations:
VersionedVendorVault#commit(currentState, actionSummary) — Saves the current VendorVault state in its history along with a summary of the action that caused the change.VersionedVendorVault#undo(currentState) — Restores the previous VendorVault state from its history.VersionedVendorVault#redo(currentState) — Restores a previously undone VendorVault state from its history.VersionedVendorVault#canUndo() and VersionedVendorVault#canRedo() — Checks if undo/redo operations are possible based on the current state of the history.Note: VendorVault stores both address book and inventory data as a single state. Even if only one is modified, the entire VendorVault state is saved, enabling a single undo/redo mechanism.
These operations are exposed in the Model interface as Model#commitVendorVault(), Model#undoVendorVault(), Model#redoVendorVault(), Model#canUndoVendorVault() and Model#canRedoVendorVault() respectively.
Given below is an example showing how the undo/redo mechanism behaves at each step.
Step 1. The user launches the application for the first time. VersionedVendorVault is initialized with the initial VendorVault state (which includes the address book and inventory internally), and the currentStatePointer points to that state.
Step 2. The user executes delete support@adafruit.com command. The delete command calls Model#commitVendorVault(), saving the modified VendorVault state to vendorVaultStateList and moving the currentStatePointer to point to the newly inserted state.
Step 3. The user executes add n/Adafruit … to add a new vendor contact. The add command also calls Model#commitVendorVault(), saving another modified VendorVault state the vendorVaultStateList.
Note: If a command fails its execution, it will not call Model#commitVendorVault(), so the state will not be saved into the vendorVaultStateList.
Step 4. The user now decides that adding the contact was a mistake, and decides to undo that action by executing the undo command. The undo command will call Model#undoVendorVault(), which will shift the currentStatePointer once to the left, pointing it to the previous state, and restores VendorVault's data to that state. The action summary is also retrieved from stateActionSummaryList and returned to the user as part of the CommandResult's feedback message.
Note: If the currentStatePointer is at index 0 (the initial state), there is no previous state to restore. The undo command checks this using Model#canundoVendorVault() and returns an error if undo is not possible.
The following sequence diagram shows how an undo operation goes through the Logic component:
Note: The lifeline for UndoCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Similarly, how an undo operation goes through the Model component is shown below:
The redo command does the opposite — it calls Model#redoVendorVault(), which shifts the currentStatePointer once to the right, pointing to the previously undone state, and restores the VendorVault to that state.
Note: If the currentStatePointer is at index vendorVaultStateList.size() - 1 (the latest state), there is no undone states to redo. The redo command checks this using Model#canRedoVendorVault() and returns an error if redo is not possible.
Step 5. The user then executes the list command. Commands that do not modify state,such as list, will not call Model#commitVendorVault(), Model#undoVendorVault() or Model#redoVendorVault(). Thus, the vendorVaultStateList remains unchanged.
Step 6. The user executes clear, which calls Model#commitVendorVault(). Since the currentStatePointer is not pointing at the end of the vendorVaultStateList, all VendorVault states after the currentStatePointer will be purged. Reason: It no longer makes sense to redo the add n/Adafruit … command. This is the behavior that most modern desktop applications follow.
The following activity diagram summarizes what happens when a user executes a new command:
Aspect: How undo & redo executes:
Alternative 1 (current choice): Saves the entire current state.
Alternative 2: Individual command knows how to undo/redo. itself.
delete, just save the person being deleted).Given that VendorVault’s data size is expected to remain relatively small (e.g. about 1,000 contacts and 5,000 products) and undoable actions are typicallyoccur in small batches, the memory overhead of storing snapshots is acceptable.
Aspect: Granularity of undo/redo scope:
Alternative 1 (current choice): Single unified VendorVault snapshot per commit.
Alternative 2: Separate versioned histories for AddressBook and Inventory.
Similarly, the extra memory cost is acceptable in exchange for simpler, more reliable undo behaviour.
Aspect: Where the commit is triggered:
Alternative 1 (current choice): Each command calls Model#commitVendorVault() itself.
Alternative 2: LogicManager commits automatically after every successful command.
We chose Alternative 1 to give commands explicit control, avoid unnecessary snapshots, and keep the undo history meaningful.
The command history feature is implemented using a CommandHistory class that maintains a list of previously executed commands. Each time a command is executed, it is added to the CommandHistory. Additionally, it implements the following operations:
CommandHistory#add(String commandText) — Adds a command as a string to the history.CommandHistory#getPrevious(String currentInput) — Returns the previous command in the history.CommandHistory#getNext(String currentInput) — Returns the next command in the history.CommandHistory#resetNavigation() — Resets the navigation pointer to the end of the history.These operations are exposed through Logic#addCommandHistory(String), Logic#getPrevCommandHistory(String), and Logic#getNextCommandHistory(String), allowing the UI to navigate command history via the Logic API without directly depending on CommandHistory.
The following class diagram summarizes the structure and relationships used by this feature:
Given below is an example usage scenario and how the command history behaves at each step.
Step 1. The user executes the command add n/Adafruit Industries.... The command is executed and added to the CommandHistory.
Note: Commands are added to the CommandHistory only if they execute successfully. This includes commands whose execution produces warnings.
Step 2. The user executes the command delete support@adafruit.com. The command is executed and added to the CommandHistory.
Step 3. The user presses the UP arrow key to navigate to the previous command. The CommandHistory#getPrevious() method is called with the current input (empty in this case). The command box is then updated with the previous command delete support@adafruit.com.
The following sequence diagram shows how the getPrevious operation works as described:
Similarly, how the getPrevCommandHistory operation goes through Logic component is shown below:
The following activity diagram below summarizes how key presses are handled to navigate through the command history:
Aspect: How command history stores input:
Alternative 1 (current choice): Maintain a list of command strings and navigate via an index.
Alternative 2: Store command objects with full state.
Since the goal is shell-like navigation, storing only strings is sufficient and keeps the implementation simple.
Aspect: How navigation handles partially typed input:
Alternative 1 (current choice): Preserve the current input as a “draft” when the user navigates through history.
Alternative 2: Ignore partially typed input and always replace with history.
Preserving draft input improves user experience and is easy to implement with minimal overhead.
The archive feature allows both vendor contacts and products to be hidden from the main lists without permanently deleting them. Archived records remain stored in the system and can be restored at any time.
The feature introduces four commands:
archive EMAIL — archives a vendor contact
restore [EMAIL] — restores an archived vendor; lists all archived vendors if no email given
archiveproduct IDENTIFIER — archives a product
restoreproduct [IDENTIFIER] — restores an archived product; lists all archived products if no identifier given
Vendor archiving — Person uses a tag-based approach: isArchived() checks whether the person's tag set contains an "archived" tag. Person#archive() returns a new Person with the tag added; Person#restore() returns a new Person with the tag removed.
Product archiving — Product uses a dedicated boolean isArchived field. Product#archive() and Product#restore() return new instances with the flag toggled accordingly.
Both sets of operations are exposed through the Model interface:
Model#archivePerson(Person person)
Model#restorePerson(Person person)
Model#archiveProduct(Product product)
Model#restoreProduct(Product product)
The ModelManager implementations call addressBook.setPerson() and inventory.setProduct() respectively to swap the old record for the newly created immutable copy.
Given below is an example of the vendor archive/restore lifecycle.
Step 1. The vendor list contains two active vendors, Alice and Bob.
Step 2. The user executes archive alice@example.com. Alice's Person object is replaced with a copy that has the "archived" tag added. Because the active filtered list excludes archived persons, Alice disappears from the main view.
Step 3. The user executes restore alice@example.com. Alice's Person object is replaced with a copy that has the "archived" tag removed. She reappears in the main list.
archiveproduct / restoreproduct follow the same lifecycle as described above, operating on Product objects in the Inventory instead of Person objects in the AddressBook.
The sequence diagram below shows the interactions within the Logic component when archive support@adafruit.com is executed:
Note: The lifeline for ArchiveCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of the diagram.
The sequence diagram below shows the interactions for restore support@adafruit.com:
Note: The lifeline for RestoreCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of the diagram.
Similarly, how an archive operation goes through the Model component is shown below:
Similarly, how a restore operation goes through the Model component is shown below:
In full, the steps for archive support@adafruit.com are:
AddressBookParser identifies the command word archive.ArchiveCommandParser parses the email argument.ArchiveCommand object is created.LogicManager executes the command.VendorVault person list (not just the filtered list) for the matching email.Model#archivePerson() is called, replacing the Person with an archived copy via Person#archive().Model#commitVendorVault() is called to save the state for undo/redo.The restore EMAIL command follows a similar flow: it searches only the archived subset of persons, calls Model#restorePerson(), then commits. If no email is provided (or the email is not found), the filtered list is switched to show only archived vendors as a convenience.
archiveproduct IDENTIFIER and restoreproduct IDENTIFIER mirror this flow against the Inventory, using Model#archiveProduct() / Model#restoreProduct().
The model also maintains two constant predicates:
PREDICATE_SHOW_ACTIVE_PERSONS = person -> !person.isArchived()
PREDICATE_SHOW_ACTIVE_PRODUCTS = product -> !product.isArchived()
These are applied by default so that archived records are hidden from the main display. When restore (without an argument) or restoreproduct (without an identifier or with an unknown identifier) is executed, updateFilteredPersonList(Person::isArchived) or updateFilteredProductList(Product::isArchived) is called temporarily to surface the archived records as a guide to the user.
Aspect: Representation of archived vendors (Person)
Alternative 1 (current choice): Use a special "archived" tag in the existing Tag set.
Alternative 2: Add a dedicated boolean isArchived field to Person (same approach used by Product).
JsonAdaptedPerson.Alternative 1 was chosen for Person to minimise changes to the existing architecture. A future refactor may unify both approaches.
Aspect: Representation of archived products (Product)
Alternative 1 (current choice): Dedicated boolean isArchived field.
JsonAdaptedProduct.Alternative 2: Reuse a tag (same approach as Person).
Alternative 1 was chosen as Product has no pre-existing tag mechanism to reuse.
The command alias feature allows users to define shorthand strings that map to existing command words.
When a user enters a command, the input is resolved against the stored alias mappings before being parsed and executed.
The core data structures are:
Alias — An immutable value object holding an alias string and its originalCommand string.AliasList — Stores a list of Alias objects, enforces uniqueness of alias string, and exposes lookup, add and remove operations.Aliases — The top level model object that wraps AliasList. It implements ReadOnlyAliases.These operations are exposed in Model interface as Model#addAlias(), Model#findAlias(), Model#removeAlias().
Alias Resolution is handled in AddressBookParser.
Before converting any command to its specific parser, the parser checks if the command word entered by the user matches any stored alias.
If a match is found, the alias is substituted with its original command word, and parsing continues as normal.
Otherwise, the input is used as it is.
Given below is an example usage scenario and how the alias feature behaves an each step.
Step 1. The user launches the application. The Aliases object is initialised and loaded from aliases.json via AliasStorage
Note: If the file does not exist, an empty AliasList is used
Step 2. The user executes alias list ls. An AliasCommand is created with originalCommand = "list" and alias = "ls".
The new Alias is stored in Aliases and persisted to aliases.json
Note: If the alias string "ls" already existed in AliasList, a DuplicateAliasException is thrown and the command fails with an error message.
Step 3. The user types ls. The AddressBookParser checks the command word "ls" against the stored aliases and finds a match. "ls" is mapped to "list".
The command word is substituted, and the rest of execution proceeds identically to if the user had typed "list" directly.
Step 4. The user types ls args. The same substitution occurs, only the command word "ls" is replaced with "list", and "args" is passed through unchanged to the underlying parser.
Step 5. The user executes deletealias ls. A DeleteAliasCommand is created and removes "ls" from Aliases.
The updated alias list is persisted to aliases.json.
Note: If "ls" does not exists in AliasList, a NoAliasFoundInAliasListException is thrown and the command fails with an error message.
The following sequence diagram shows how the user input ls is resolved through AddressBookParser and Aliases:
Aspect: Where alias resolution happens
Alternative 1 (current choice): Resolves aliases in AddressBookParser before parsing it to the correct command.
ModelAlternative 2: Resolve aliases in LogicManager before passing the input to the parser.
Model dependencies.LogicManager to be aware of alias resolution logic, making the code more complex.Alternative 1 is preferred as it keeps all parsing logic in one place.
Aspect: Persistence strategy
Alternative 1 (current choice): Store aliases in a separate aliases.json file via AliasStorage.
Alternative 2: Embed alias data inside addressbook.json.
Alternative 1 aligns with the existing storage pattern used by contacts and inventory.
This feature upgrades contact/product search to use partial matching and show matches by relevance. It is implemented through a match predicate and shared ranking contract:
NameContainsKeywordsScoredPredicate tests if a contact's name matches any keyword using partial matching. The name is split and processed as tokens.
toScore(String token, String keyword, String fullName) checks each keyword against each token in the name to determine a score.computeScore(Person person) returns the best score among all keyword-token pairs using SCORE_COMPARATOR.createPersonComparator() returns a comparator that ranks contacts by their score.The same applies for ProductNameContainsKeywordsScoredPredicate.
RelevanceRank defines the ranking contract.
EXACT_TOKEN > PREFIX_TOKEN > SUBSTRING_TOKEN > NO_MATCH.Score(MatchTier tier, int unmatchedCharCount, String sortKey) represents how relevant a match is.SCORE_COMPARATOR implements score comparison.This diagram shows the structure and dependency of Better Search classes:
This diagram shows an example of scoring state when the given keyword is "adafruit":
SCORE_COMPARATOR compares scores in this order: tier, unmatchedCharCount, sortKey (alphabetical). This
gives the ranking: "Adafruit", "Adafruity", "Tadafruit", "Cytron".
Here's how Better Search is executed by the find command:
Step 1. FindCommandParser creates a FindCommand with a NameContainsKeywordsScoredPredicate.
Step 2. FindCommand calls ModelManager#updateFilteredPersonList to use the predicate.
Step 3. FindCommand then creates a VendorEmailMatchesContactsPredicate, using it to call
ModelManager#updateFilteredProductList.
These updates trigger UI to display matching contacts and their products.
This diagram shows the interactions between Logic and Model:
This diagram summarises the decision flow in Model:
The usage scenario for findproduct is analogous.
Aspect: Matching strategy
Alternative 1 (current choice): Partial matching
Alternative 2: Exact matching
Alternative 1 was chosen to ensure discoverability and improve user experience.
Aspect: Ranking implementation
Alternative 1 (current choice): Shared contract between contact and product entities.
Alternative 2: Independent logic per entity.
Alternative 1 was chosen for consistency and maintainability.
Target user profile:
Small business owners who:
Value proposition:
VendorVault helps small business owners seamlessly manage vendor contacts and track inventory in one simple system. By flagging and sorting low-quantity products, owners instantly know what needs restocking and who to contact, enabling timely action without relying on complex or costly inventory tools.
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 guide | recap and learn commands |
* * * | user | add a new contact | add new vendors I work with |
* * | user | edit an existing contact | keep vendor information up to date |
* * * | user | delete a contact | remove vendors I no longer work with |
* * * | user | view contacts | |
* * | user | find a contact by name | locate their details without having to go through the entire list |
* | user | sort contacts by name | browse them easily |
* * | user | clear all contacts | reset my vendor list |
* * * | user | add a product | add new products I sell |
* * | user | edit an existing product | keep product information up to date |
* * * | user | delete a product | remove products I no longer sell |
* * * | user | view products | |
* * | user | find a product by name | locate their details without having to go through the entire list |
* | user | sort products by name/identifier | browse them easily |
* * | user | clear all products | reset my inventory |
* * | user | view inventory statistics | understand my product quantity levels |
* * | user | undo changes I made | easily revert and correct mistake |
* * | user | redo changes I made | easily reapply changes I accidentally undid |
* * | user | navigate my previous commands | reuse or correct recent commands without providing them again |
* * | expert user | add alias for commands | create alias for long commands according to my preferences |
* * | expert user | delete alias for commands | remove alias for I no longer want to use |
* * | expert user | view aliases for commands | view all aliases that I have set |
(For all use cases below, the System is the VendorVault application, referred to as VV and the Actor is the User, unless specified otherwise)
Use case: UC1 - Add a Vendor Contact
Preconditions: Application is running, user is on the main screen.
MSS
Use case ends.
Extensions
1a. VV detects invalid command format
Steps 1a1–1a2 are repeated until the command format is valid.
Use case resumes from step 1.
1b. VV detects error in fields provided
1b1. VV rejects the command and displays validation error message.
1b2. User re-enters the corrected fields.
Steps 1b1–1b2 are repeated until the fields are valid.
Use case resumes from step 1.
1c. VV detects duplicate contact
1c1. VV rejects the command and displays a duplicate contact error message.
1c2. User re-enters the corrected fields.
Steps 1c1–1c2 are repeated until the fields are not a duplicate.
Use case resumes from step 1.
1d. VV detects potential duplicate contact
1d1. VV accepts the command and displays a warning with details of the similar contact.
Use case resumes from step 2.
1e. VV detects potential input mistake
1e1. VV accepts the command displays a warning indicating the input may be unintended.
Use case resumes from step 2.
Use case: UC2 - Edit a Vendor Contact
Preconditions: Application is running, user is on the main screen.
MSS
Use case ends.
Extensions
*a. All extensions that apply to UC1: Add a Vendor Contact also apply here.
1f. VV detects that the operation removes all tags.
1f1. VV requests confirmation.
1f2. User confirms the deletion.
Use case resumes from step 2.
1f2a. User cancels the deletion instead.
1f2a1. VV aborts the edit operation and displays a cancellation message.
Use case ends.
Use Case: UC3 - View Vendor Contacts
Preconditions: Application is running, user is on the main screen.
MSS
Use case ends.
Use case: UC3 - Delete Vendor Contact
Preconditions: Application is running, user is on the main screen and has added a contact.
MSS
Use case ends.
Extensions
2a. User decides not to delete the contact, rejecting the deletion.
Use case ends.
Use case: UC4 - Find Vendor Contact
Preconditions: Application is running, user is on the main screen and has added a contact.
MSS
Use case ends.
Extensions
1a. User provides no keyword.
Use case ends.
Use Case: UC5 - Add Product
Preconditions: Application is running, user is on the main screen.
MSS
Use case ends.
Extensions
2a. VV detects error in provided data (e.g. missing compulsory fields, invalid data format).
Steps 2a1–2a2 are repeated until all fields are valid.
Use case resumes from step 4.
3a. VV detects duplicate product.
Steps 3a1–3a2 are repeated until a unique ID is provided.
Use case resumes from step 5.
6a. Storage file cannot be written or accessed.
Use case ends.
Use Case: UC6 - View Products
Preconditions: Application is running, user is on the main screen.
MSS
Use case ends.
Use case: UC7 - Delete Product
Preconditions: Application is running, user is on the main screen and has added a product.
MSS
Use case ends.
Extensions
2a. User decides not to delete the product, rejecting deletion.
Use case ends.
Use case: UC8 - Find Product
Analogous to UC4.
Use case: UC9 - Undo/Redo a Change
Preconditions: Application is running, user is on the main screen, and at least one undoable action has been performed in the current session.
MSS
Use case ends.
Extensions
1a. VV detects that no undoable actions exist in the current session.
1a1. VV displays an error message indicating there is nothing to undo.
Use case ends.
2b. User performs a new undoable action after undoing a previous action.
2b1. VV clears the redo history.
2b2. The new action becomes the latest undoable action.
Use case ends.
3a. VV detects that no redoable actions exist (e.g. redo history was cleared, or no undo was performed).
3a1. VV displays an error message indicating there is nothing to redo.
Use case ends.
Use case: UC10 - Navigate Command History
Preconditions: Application is running, user is on the main screen.
MSS
Use case ends.
Extensions
*a. VV detects that no command history exists (no commands have been entered in this session).
*a1. VV does nothing.
Use case ends.
1a. User is already at the oldest command in the history.
1a1. VV does nothing.
Use case resumes from step 2.
3a. User is already at the most recent command in the history.
3a1. VV does nothing.
Use case resumes from step 4.
Usability:
Reliability:
Portability:
17 or above installed.Performance:
Persistence:
Documentation:
Security:
Scalability and Capacity:
Maintainability:
Testability:
Accessibility:
Initial launch as per Quick Start
Saving window preferences
Resize the window as desired. Move the window to a different location. Close the window.
Re-launch app. Expected: The most recent window size and location is retained.
Prerequisites: There should be no contact with email support@adafruit.com.
Test case: add n/Adafruit Industries p/64601234 e/support@adafruit.com a/151 Varick St, New York, NY 10013, USA
Test case: add
Invalid Command Format.. error.Test case add e/support@adafruit.com
Missing required field(s): n/ (name), p/ (phone), a/ (address) error.Test case add n/Adafruit p/64601234 e/support@adafruit.com a/USA
support@adafruit.com has been previously added.This vendor contact already exists with the same email (name: Adafruit Industries, email: support@adafruit.com).Prerequisites: There should be a contact with the email support@adafruit.com with an address different from New York, USA and no contact with email sg.sales@cytron.io.
Test case: edit support@adafruit.com a/New York, USA
New York, USA.Test case: edit support@adafruit.com e/
Email should not be blank error.Test case: edit sg.sales@cytron.io a/USA
No contact with the specified email was found error.Test case: edit support@adafruit.com
At least one field to edit must be provided error.Prerequisites: There should be 2 contacts with the emails support@adafruit.com and sales@techsource.com.
Test case: delete support@adafruit.com
y to confirm and delete the matching contact.Test case: delete -y sales@techsource.com
Test case: delete notfound@example.com
notfound@example.com.No contact with the specified email was found error.Test case: delete
Invalid Command Format.. error.Prerequisites: There should be a contact named Adafruit Industries
Test case: find
Invalid command format! … errorTest case: find ada
Prerequisites: There should be multiple contacts in the system. (You can verify with list)
Test case: clear
y to confirm and clear all contacts.Test case: clear -y
Prerequisites: There should be a contact with email support@adafruit.com
Test case: addproduct id/SKU-1003 n/Arduino Uno R4 q/50 th/10 e/support@adafruit.com
Test case: addproduct id/SKU-288
Missing required field(s): n/ (product name) errorTest case: addproduct id/SKU-1003 n/HP LaserJet (M428fdw) q/17 th/15 e/support@adafruit.com
This product already exists with the same identifier. errorTest case: addproduct id/SKU-1004 n/HP LaserJet (M428fdw) q/17 th/15 e/sg.sales@cytron.io
Vendor email sg.sales@cytron.io does not match any existing contact. errorPrerequisites: There should be 2 product with ids SKU-1001 and SKU-1002 in product list
Test case: deleteproduct SKU-1001
y to confirm and delete the matching product.Test case: deleteproduct -y SKU-1002
Test case: deleteproduct NoValidIdentifier
NoValidIdentifier.No product found with the specified identifier error.Test case: deleteproduct
Invalid Command Format.. error.Prerequisites: There should be a product named Arduino Uno R4
Test case: findproduct
Invalid command format! …Test case: findproduct uno
Prerequisites: There should be multiple products in the system. (You can verify with listproduct)
Test case: clearproduct
y to confirm and clear all products.Test case: clearproduct -y
Prerequisites: None
Test case: threshold
Invalid command format! …Test case: threshold 5
Default restock threshold set to: 5 successTest case: addproduct id/DE/5 n/PlayStation q/0 e/sg.sales@cytron.io
Note: Perform these test cases in order within the same app session.
Prerequisites: ls is not a valid alias.
Test case: alias
Test case: alias list ls
ls is created that maps to the list command. The alias can now be used instead of list.Test case: deletealias ls
ls will be removed.Test case: alias command cmd
Command does not exists or is not supported error.Test case: alias list
Invalid Command Format.. error.Test case: deletealias
Invalid Command Format.. error.Note: Perform these test cases in order within the same app session.
Prerequisite: add n/Adafruit Industries p/64601234 e/support@adafruit.com a/151 Varick St, New York, NY 10013, USA and verify contact appears in contact list.
Test case: undo adding a contact
Undo successful: Reverted the addition of contact: … and the contact no longer appears in contact list.Test case: redo undoing adding a contact
Redo successful: Reapplied the addition of contact: … and the contact reappears in contact list.Prerequisite: addproduct id/SKU-288 n/HP LaserJet (M428fdw) q/17 th/15 and verify product appears in contact list.
Test case: undo adding a product
Undo successful: Reverted the addition of product: … and the product no longer appears in product list.Test case: redo undoing adding a product
Redo successful: Reapplied the addition of product: …and the product reappears in product list.At the end, run listall and verify both added contact and product are present in the contact and product lists.
Prerequisites: App is running
Test case: Run a command that modifies data and restart the app
Test case: Delete /data/addressbook.json and restart the app
Test case: Enter invalid JSON to /data/inventory.json and restart the app
WARNING: Error reading from jsonFile is logged to the terminal and app starts with empty inventory/preferences.json and restart the app
WARNING: Error reading from jsonFile file preferences.json logged to the console and app starts with default preferencesWhile AB3 deals with one entity, our app handles and integrates two distinct entities.
Hard to navigate initially due to the large inherited AB3 codebase and layered architecture.
Required deeper understanding of system design rather than simple feature additions.
Extensive testing needed due to increased system complexity.
Linking contacts and products while ensuring consistent behavior across both domains required careful architectural design.
Consistently ensuring code is tested before merging added overhead to the development workflow.
Higher implementation effort required from us due to more complex scope; each member contributed about 6k LoC
Deepened existing features and created new features that complement them
Overcoming integration challenges to keep code and documentation aligned
Each team member implemented at least 1 complex feature while managing concurrent high-workload modules
Consistently kept up with project progress through regular updates and communication despite competing priorities
Team size: 4