We have used AI tools to help us with the ideation phase and to assist us in some parts of the codebase. Some of the localized logic (i.e the parsing logic for AddTagCommandParser, TagRegistry constructor) and some of the tests have been inspired and/or adapted. However, before commiting, we always manually verified all code pushed to ensure no regressions.
Refer to the guide Setting up and getting started.
The Architecture Diagram above explains the high-level design of the App.
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.
The next meeting information is modelled as a dedicated value object (seedu.address.model.person.NextMeeting) so that validation, defaults, and equality checks stay consistent across the code base.
Parsing workflow
CliSyntax.PREFIX_NEXT_MEETING (m/) marks the optional input.AddCommandParser falls back to NextMeeting.DEFAULT_VALUE ("No meeting scheduled") when the prefix is absent, while ParserUtil.parseNextMeeting trims and validates non-empty input.EditCommandParser maps the prefix into EditCommand.EditPersonDescriptor, enabling partial updates during edit.Model and storage
Person stores a NextMeeting instance alongside existing identity and data fields.JsonAdaptedPerson serialises/deserialises the value, substituting the default string when the JSON omits the field so older save files continue to load.SampleDataUtil provides illustrative values so that the UI demonstrates the field out of the box.UI
PersonCard now renders a nextMeeting label inside PersonListCard.fxml, showing Next meeting: <value> so users can see at-a-glance follow-up details.Scope decision (v1.5): We experimented with a richer
Meetingaggregate in v1.4 but removed it after integration issues. KeepingNextMeetingas a single value object lets us ship a reliable reminder while we refine the structured design for a later release.
Automated coverage lives in AddCommandParserTest, EditCommandParserTest, ParserUtilTest, and PersonTest, ensuring the prefix behaves correctly, invalid values are rejected, and equality semantics include the new field.
addtag and listtag)MeetCLI extends the base AddressBook3 logic to support tag management commands.
Users can now add new tags to specific contacts and view all existing tags in alphabetical order.
The diagram above illustrates how the new AddTagCommand and ListTagCommand integrate with the existing architecture:
Command class.LogicManager orchestrates their execution via the Model interface.ModelManager implements the tag-related operations, delegating to the TagRegistry and Person classes.AddTagCommand updates a contact’s tag list, while ListTagCommand queries and aggregates unique tags.This separation maintains the command pattern used throughout the app and keeps the tag functionality modular and testable.
Target user profile:
Value proposition: Manage and retrieve contacts faster than typical GUI apps, with features tailored for grouping, filtering, and organizing by context (academic modules, CCAs, or work projects).
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 | learn how to use the app quickly |
*** | user | add a new contact | keep track of people I know |
*** | user | delete a contact | remove outdated or irrelevant entries |
*** | user | search for a contact by name | find details quickly |
*** | user | tag contacts by group/module | organize contacts into categories |
** | student | filter contacts by tag | view only project/module teammates |
** | user with many contacts | sort contacts by name | locate people easily |
** | busy user | import/export contacts from a file | avoid retyping data |
* | user | set reminders for specific contacts | remember to follow up |
Main Success Scenario (MSS):
add n/<name> p/<phone> e/<email> a/<address> [c/<company>] [m/<meeting>] [t/<tag>...].Extensions:
Main Success Scenario (MSS):
edit INDEX [n/<name>] [p/<phone>] [e/<email>] [a/<address>] [c/<company>] [m/<meeting>] [t/<tag>...].Extensions:
Main Success Scenario (MSS):
find [KEYWORD…] [t/<tag>...] [c/<company>...].Extensions:
Main Success Scenario (MSS):
listtag.Extensions:
Main Success Scenario (MSS):
clear.Extensions:
Main Success Scenario (MSS):
Extensions:
Main Success Scenario (MSS):
t/CS2103T) to a specific contact.Extensions:
Predicate<Person> is used extensively in filtering operations (e.g., find command) to determine which contacts match user-specified criteria. Multiple predicates can be combined with logical AND/OR to create complex filters.add, edit, and find commands by rejecting tags not in the registry."No meeting scheduled" when omitted. This field allows users to maintain a single, free-form reminder per contact for lightweight follow-up tracking.The find feature was extended to support name, tag, and company filtering in one command, plus a registry check that prevents searching with disallowed tags.
Syntax: find [KEYWORD…] [t/TAG…] [c/COMPANY…]
Semantics:
t/ are AND (must have all).c/ are OR (match any).Allowed-tag validation: if any requested tag is not in TagRegistry, the command shows a warning and does not update the filtered list.
FindCommand
Holds the composed Predicate<Person> and a List<String> requestedTags used only for the registry check and message formatting.
On execute(model), if requestedTags is non-empty:
model.getTagRegistry().isAllowed(tag) for each.CommandResult (no call to updateFilteredPersonList).model.updateFilteredPersonList(predicate) and returns summary via Messages.getMessageForPersonListShownSummary(count).FindCommandParser
Tokenizes with PREFIX_TAG (t/) and PREFIX_COMPANY (c/).
Builds three predicates:
NameContainsKeywordsPredicate (OR across keywords).Set<Tag> contains all required tags).Combines present predicates with AND.
Validates tag format using Tag.isValidTagName (parser-level); allowed-list is validated in FindCommand.execute.
Returns new FindCommand(combinedPredicate, rawTags) (passes raw tag names for the runtime registry check).
Design choice: format vs. policy. We keep format validation (e.g., illegal characters in a tag) in the parser, and policy validation (allowed vs. disallowed tag names) in the command at runtime so we can produce a non-fatal, user-friendly warning without throwing a parse error.
Empty query: if no keywords, no tags, and no companies → parser throws usage error.
Empty t/ or c/ values: parser throws usage error (e.g., find t/ ).
Invalid tag format: parser throws ParseException with Tag.MESSAGE_CONSTRAINTS.
Disallowed tag(s): command returns
Cannot filter by invalid tag(s): <names>. Allowed tags: [<registry contents>]
and leaves the list unchanged.
Equality and tests: FindCommand.equals includes both the predicate and the requestedTags list to avoid accidental equality between semantically different invocations.
Parser tests: cover
Command tests: cover
CommandResult, no update to FilteredList,toString() includes requestedTags (e.g., empty list [] for name-only).LogicManager#execute("find …")
AddressBookParser#parseCommand → FindCommandParser#parse
FindCommand created with composed Predicate<Person> + requestedTags
FindCommand#execute(model):
TagRegistry → warn ormodel.updateFilteredPersonList(predicate) → summary via Messages.getMessageForPersonListShownSummary(count)