I still don't get it...

Description

I would implemented like this:
  1. create a non project item table similar to itemchange
  2. Create new member functions to ItemChange:
 
 public function isViewedByUser($user=NULL) 
 {
   global $auth;
   if(is_null($user)) {
     if($auth->cur_user) {
       $user= $auth->cur_user;
     }
     else {
       return NULL;
     }
   }
   require_once("db/class_itemperson.inc.php");
   if($view= ItemPerson::getByPersonItem($user->id, $this->id)) {
      $view-> viewed ++;
      $view->update();     
   }
   else {
     $new_view= new ItemPerson(array(
        'item'=> $this->id,
        'person'=> $user->id,
        'viewed' => 1,
        ### ... some more here ...
     ));
     $new_view->insert();
   }
 }

 public function wasViewedByUser($user= NULL) {
   ###... similar like above function
 }


  1. In direct view pages (taskView, personView, commentView, effortView, companyView, projectView, fileView, fileDownload, fileDownloadAsImage) add line...
$item->ViewedByUser();
  1. In list rendering functions add line like:
  if($viewed= ItemPerson::getByItemPerson($obj->id, NULL)) {
     ### do some special stuff here like...
     if($viewed->last_time <= $obj->modified) {
        ### highlight...
     }

     if($viewed->monitor && $viewed->last_time <= $obj->modified) {
        ### very important, because monitored item was changed ###
     }
  }
  else {
     ### highlight because not viewed yet
  }

There is no need to check for visibility.

Performance...π

If performance will be too slow, we could query to itemperson-fields directly to the Item-Class similar like an Item is constructed from the Item-Part and the actual data part (like 'Task'). But this will be some incredibly complex code...