Code

Refactor main.go — the app holds MANY records

The second milestone — your app goes from one record to a shelf of them. The menu loop's skeleton stays; it becomes a slice, and the actions become Add / List / Mark.

One value becomes a slice:

	var items []Item // a whole SHELF of records now, not just one

The menu pivots to three list actions:

		fmt.Println("\n1) Add  2) List  3) Mark read  q) Quit")

1) Add — validate the title (your validateTitle from lesson 7), read the pages, then append:

		case "1":
			fmt.Print("Title: ")
			title := readLine()
			if msg := validateTitle(title); msg != "" {
				fmt.Println(msg)
				continue
			}
			pages := readInt("Pages: ")
			if pages <= 0 {
				fmt.Println("Pages must be a positive number.")
				continue
			}
			items = append(items, Item{Title: title, Pages: pages})
			fmt.Println("Added:", title)

3) Mark read — reads a number and changes the element through the index, not a copy:

		case "3":
			n := readInt("Item number: ")
			if n < 1 || n > len(items) {
				fmt.Println("No item with that number.")
				continue
			}
			items[n-1].Read = true // the REAL element, not a range copy
			fmt.Println("Marked as read:", items[n-1].Title)

2) List — a new listItems that ranges the slice; your aligned formatItem finally pays off as a table:

func listItems(items []Item) {
	if len(items) == 0 {
		fmt.Println("No items yet.")
		return
	}
	for i, it := range items {
		fmt.Printf("%2d. %s\n", i+1, formatItem(it))
	}
}

Two single-item pieces step aside — deliberately, not by accident: printItem retires (listItems took over its job, calling your formatItem directly), and progressPercent waits — it works on ONE item, so in the sample it has nothing to point at. In YOUR app, keep it: apply it to an item picked by number, the same way "Mark read" picks one (that's part of the homework).

Verify — add two multi-word titles, then list:

> 1
Title: War and Peace
Pages: 1225
Added: War and Peace

> 1
Title: Dune
Pages: 412
Added: Dune

> 2
 1. War and Peace                    0/1225  [reading]
 2. Dune                             0/412  [reading]

The titles read in full (spaces included), and formatItem's columns line up into a table — exactly what lesson 7's alignment was for.

Gotcha (the range copy). for _, it := range items { it.Read = true } compiles and does nothingit is a copy of each element. To change the real one, reach it by index: items[n-1].Read = true. The same copy trap as the struct in lesson 4, now inside a loop.