PROJECT: Mark

1. Overview

Mark is designed for those who prefer to use a desktop application for managing bookmarks as well as tasks related to web pages. More importantly, it is optimized for those who prefer to work with a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI).

Mark is morphed from an existing desktop Java application Address Book (Level 3) and several enhancements were made to it over a period of 8 weeks by my team of 5 software developers. The following sections document all the contributions that I have made to Mark.

2. Summary of Contributions

Here is a summary of my personal contributions to the team project over the 8 weeks. All my code contributions can be found here.

2.1. Major Enhancement: Undo/Redo a specific number of previous commands

  • What it does: This feature allows the user to undo a given number previous undoable commands (i.e. commands that alter the state of bookmarks or reminders). Previous undo commands can also be reversed by specifying the number of actions the undo command has undone. A list of actions undone/redone by the user will also be shown to the user as a command result displayed in the result display box after each undo/redo command.

  • Justification: This feature improves Mark significantly because a user can make mistakes in commands and the application should provide a convenient way to rectify them. In addition, allowing users to specify the number of actions to undo/redo greatly improves users' productivity as they can now undo/redo multiple actions using one command instead to typing undo/redo repeatedly.

  • Highlights: This enhancement affects existing commands and commands to be added in future. An in-depth analysis of design alternatives was required. The implementation too was challenging as it required changes to existing commands.

  • Credits: The basic idea is adapted from AddressBook (Level 4)

2.2. Minor Enhancements

  • Added a goto command that can immediately open a bookmark in the embedded web view of the app

  • Added support for opening any bookmark by double clicking the bookmark in either bookmark list, folder tree view or favorite bookmark list in the GUI

  • Added a favorite command that can add an existing bookmark to the list of favorite bookmarks and a separate list of of favorite bookmarks to the dashboard tab of the app

  • Improved the existing add command to support bookmarking current web page by supplying a keyword this in place of the URL field

  • Improved the existing find command to support filtering bookmarks by tags and/or folders

2.3. Other Contributions

  • Project management:

    • Set up the team organisation and team repo

    • Managed issue tracker and milestones for team repo

    • Managed release v1.3 on GitHub

  • Documentation:

    • Migrated Developer Guide from Address Book to Mark and added the user stories: #7

  • Community:

    • PRs reviewed (with non-trivial reviews): #14, #66, #73, #75, #112, #153
      A complete list of PRs reviewed can be found here.

    • Reported bugs and suggestions for the team project: #85, #102, #170

  • Tools:

    • Integrated several CI tools (Travis, AppVeyor) to the team repo

    • Added additional tools (Codacy, Netlify and Coveralls) to the team repo

    • Added some useful GitHub plugins (WIP bot, PR Triage) to the team repo

3. Contributions to the User Guide

I wrote about the usage of goto, favorite, undo and redo commands for the User Guide. Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users.

3.1. Opening a bookmark: goto

This command opens the specified the bookmark from the bookmark manager.

Format: goto INDEX

For example:

  • list
    goto 1
    Opens the first bookmark in the bookmark manager.

Parameter constraints:

  • Opens the bookmark at the specified INDEX.

  • The index refers to the index number shown in the displayed bookmark list.

  • The index must be a positive integer 1, 2, 3, …​

3.2. Adding a bookmark to Favorites: favorite|fav

This command adds a Favorite tag to the specified bookmark.

Format: favorite|fav INDEX

For example:

  • If you want to add the first bookmark in the bookmark list to your favorite bookmarks, simply input favorite 1, a Favorite tag will be added to the fist bookmark and the bookmark will also appear in the favorite bookmark list in the dashboard.

  • Alternatively, you can also type a shorter command fav 1 to add the first bookmark to your favorite bookmarks as well.

Parameter constraints:

  • fav is an alias for favorite

  • Removing the Favorite tag is the same as removing a normal tag from a bookmark (see [editing-bookmarks])

  • The index refers to the index number shown in the displayed bookmark list.

  • The index must be a positive integer 1, 2, 3, …​

3.3. Undoing previous commands: undo

If you mistakenly enter a command that permanently changes Mark, do not worry, you can always use undo to rectify the mistake! This command restores Mark to the state before the given number of previous undoable commands were executed.

Format: undo [STEP=1]

For example:

  • When you accidentally delete a wrong bookmark and you want to bring it back. There is no need for you to manually add that bookmark back as undo will do the magic for you. Just input undo in the command box, the deleted bookmark will reappear in your bookmark list.

  • Suppose you have entered two delete commands, delete 1 and delete 2, and now you want to get back both bookmarks, a possible way is enter undo twice. Besides, Mark also offers an alternative where you can just type undo 2 to undo these two commands.

Parameter constraints:

  • STEP must be a positive integer 1, 2, 3, …​

  • Undoable commands include commands that modify the bookmark list, folders, reminders or annotations, which includes add, edit, delete, clear, reminder, folder, annotate, etc).

3.4. Redoing previously undone commands: redo

This command reverses the given number of undone actions.

Format: redo [STEP=1]

Examples:

  • redo
    Reverses the most recent undo command.

  • delete 1
    delete 2
    undo 2
    redo 2
    The redo 2 command restores the bookmark list to the state before the undo 2 command was executed. You will see the two bookmarks you just recovered get deleted again.

Parameter constraints:

  • STEP must be a positive integer 1, 2, 3, …​

4. Contributions to the Developer Guide

I modified the section about the logic component in the Developer Guide and added the description of the architecture and some use cases of Undo/Redo feature to the Developer Guide as well. Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project.

4.1. Logic component

The following class diagram illustrates the structure of the Logic component:

LogicClassDiagram
Figure 1. Structure of the Logic Component

API : Logic.java

Logic uses the MarkParser class to parse the user command. This results in a Command object which is executed by the LogicManager. The command execution can affect the Model (e.g. adding a bookmark) and/or Storage (e.g. import bookmarks). The result of the command execution is encapsulated as a CommandResult object which is passed back to the Ui. In addition, the CommandResult object can also instruct the Ui to perform certain actions, such as displaying help to the user, switching the tab view, or exiting the application.

As an example of how the Logic component works, the following sequence diagram shows the interactions within the Logic component for the execute("delete 1") API call:

DeleteSequenceDiagram
Figure 2. Interactions Inside the Logic Component for the delete 1 Command
The lifeline for DeleteCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

The Logic component does the following:

  • Parses and executes user commands

  • Modifies Model and/or Storage according to user commands

  • Instructs Ui through CommandResult objects

4.2. Undo/Redo feature

The Undo and Redo feature is crucial to give users the flexibility of reverting wrongly executed commands. This section explains the implementation of the undo/redo mechanism as well as some design considerations when implementing this feature.

4.2.1. Implementation

The undo/redo mechanism is facilitated by VersionedMark. It extends Mark with an undo/redo history, stored internally as a list of markStateRecord and currentPointer. The class diagram for MarkStateRecord and its relationship with ReadonlyMark, Mark and VersionedMark is shown below:

MarkStateRecordClassDiagram

As shown in the class diagram, VersionedMark implements the following operations:

  • VersionedMark#save(String record) — Saves the current Mark state and the corresponding record to its history.

  • VersionedMark#undo(int step) — Undoes the given number of actions and returns the record which consists of the list of actions being undone.

  • VersionedMark#redo(int step) — Redoes the given number actions and returns the record which consists of the list of actions being undone.

These operations are exposed in the Model interface as Model#saveMark(String record), Model#undoMark(int step) and Model#redoMark(int step) respectively.

Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.

Step 1. The user launches the application for the first time. The VersionedMark will be initialized with the initial Mark state and an empty record, and the currentPointer pointing to that single state record.

UndoRedoState0

Step 2. The user executes delete 5 command to delete the 5th bookmark in the Mark. The delete command calls Model#saveMark(String record), causing the modified state of the Mark and the record of the command after command execution to be saved in the markStateRecords, and the currentPointer is shifted to the newly inserted state record.

UndoRedoState1

Step 3. The user executes add u/www.google.com …​ to add a new bookmark. The add command also calls Model#saveMark(String record), causing another modified Mark state and the record to be saved into the markStateRecords, and the currentPointer is again shifted to the newly inserted state record..

UndoRedoState2
If a command fails its execution, it will not call Model#saveMark(String record), so the Mark state will not be saved into the markStateRecords.

Step 4. The user now decides that adding the bookmark was a mistake, and decides to undo that action by executing the undo command. The undo command will call Model#undoMark(1) since by default the number of steps to undo is 1 if not specified. This command shifts the currentPointer once to the left, pointing it to the previous state record, and restores the Mark to that state.

UndoRedoState3
If the currentPointer is at index 0, pointing to the initial Mark state, then there are no previous Mark states to restore. The undo command uses Model#canUndoMark(1) to check the if there are enough commands to undo. In this case, it will return an error to the user rather than attempting to perform the undo.

The following sequence diagram shows how the undo operation works:

UndoSequenceDiagram
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.

The redo command does the opposite — it calls Model#redoMark(int step), which shifts the currentPointer to the right by the given number of steps, and restores the Mark to the state corresponding to the currentPointer.

If the currentPointer is at index markStateRecords.size() - 1, pointing to the latest Mark state, then there are no undone Mark states to restore. The redo command uses Model#canRedoMark(1) to check the if there are enough commands to redo. In this case, it will return an error to the user rather than attempting to perform the redo.

Step 5. The user then decides to execute the command list. Commands that do not modify the Mark, such as find, will usually not call Model#saveMark(String record), Model#undoMark(int step) or Model#redoMark(int step). Thus, the markStateRecords remains unchanged.

UndoRedoState4

Step 6. The user executes clear, which calls Model#saveMark(String record). Since the currentPointer is not pointing at the end of the markStateRecords, all state records after the currentPointer will be purged. We designed it this way because it no longer makes sense to redo the add u/www.google.com …​ command. This is the behavior that most modern desktop applications follow.

UndoRedoState5

The following activity diagram summarizes what happens when a user executes a new command:

SaveMarkActivityDiagram

4.2.2. Design Considerations

This section explains some of the design considerations we had when implementing this feature.

Aspect: How undo & redo executes
  • Alternative 1 (current choice): Saves the entire Mark state.

    • Pros: Easy to implement.

    • Cons: May have performance issues in terms of memory usage.

  • Alternative 2: Individual command knows how to undo/redo by itself.

    • Pros: Will use less memory (e.g. for delete, just save the bookmark being deleted).

    • Cons: Must ensure that the implementation of each individual command are correct.

Aspect: Data structure to support the undo/redo commands
  • Alternative 1 (current choice): Use a list to store the history of Mark state records.

    • Pros: Undo/redo multiple commands can be achieved in O(1) time by just shifting the current pointer.

    • Cons: Clear state records after the current pointer can take O(n) time.

  • Alternative 2: Use two stacks, an undo stack and a redo stack to store the history of Mark state records.

    • Pros: Clear state records after the current pointer can be achieved in O(1) time by clearing the redo stack.

    • Cons: Undo/redo multiple commands can take O(n) time as each undo/redo action requires popping a state record from the undo stack and pushing it into the redo stack. There is no way to pop multiple state records efficiently at one time.

Aspect: Commands to include for undo/redo
  • Alternative 1 (current choice): Include only commands that modify the state of Mark data.

    • Pros: Only meaningful app data is stored, less memory is consumed

    • Cons: Unable to retrieve commands that do not alter the state of Mark, such as find, list

  • Alternative 2: Include all commands

    • Pros: All commands can be undone and redone

    • Cons: More data to be stored and these data might not be meaningful to the users. More data related to Command class also needs to be stored and VersionedMark might need to depend on Logic which breaks the layered architecture of the existing application

4.3. Use Cases for Undo/Redo

4.3.1. Use case: UC09 - Undo previous undoable commands

MSS

  1. User requests to undo the previous undoable command.

  2. Marks restores the list to the state before the previous undoable command was executed.

    Use case ends.

Extensions

  • 1a. There is no command to undo.

    Mark shows an error message.

    Use case ends.

4.3.2. Use case: UC10 - Redo previously undone commands

MSS

  1. User requests to redo the previously undone command.

  2. Marks reverses the most recent ‘undo’ command.

    Use case ends.

Extensions

  • 1a. There is no previous undo command to redo.

    Mark shows an error message.

    Use case ends.